← KD100

Restructure OSD layout: move wheel info to top, fix minimal view sizing

b7c0f7d07376c5e7c5a9a44774a19750972e9af0 · Claude · Feb 7, 2026 12:02

- Move wheel set indicator boxes inline with mode line at top of both
  views (was at bottom of expanded view only)
- In sequential mode, set boxes render grayed out instead of hidden
- Replace L/R direction labels with actual function names and > marker
  for active position (e.g., "Set 1:  > Brush Size  |    Opacity")
- Fix minimal view height calculation to properly fit mode line,
  function pair line, "Recent Actions:" header, and all 3 action lines
- Remove separate wheel info section from bottom of expanded view;
  all wheel state now displayed in the shared top section
- Both views now share the same mode + wheel state rendering block
  for consistent display

https://claude.ai/code/session_019MPUMMqDBcEYr7qFBLzJd6

+81 −131 · 1 file changed

  • src/osd.c +81 −131
diff --git a/src/osd.c b/src/osd.c
index a4ec5a7..9f701d0 100644
--- a/src/osd.c
+++ b/src/osd.c
@@ -326,25 +326,30 @@ void osd_set_mode(osd_state_t* osd, osd_mode_t mode) {
 
     // Update window size based on mode (dynamically calculated from font size)
     if (mode == OSD_MODE_MINIMAL) {
-        // Minimal: title + mode indicator + wheel info + 3 action lines + padding
-        osd->width = (int)(250 * scale);
-        osd->height = title_height + padding + line_height + (int)(5 * scale)
-                     + line_height + (int)(5 * scale)  // wheel set + direction info
-                     + (3 * line_height) + padding;
+        // Minimal: title + mode/set line + function pair line + "Recent Actions:" header + 3 actions + padding
+        osd->width = (int)(260 * scale);
+        osd->height = title_height + padding
+                     + line_height + (int)(3 * scale)   // mode + set indicator line
+                     + line_height + (int)(3 * scale)   // function pair line
+                     + line_height + (int)(5 * scale)   // "Recent Actions:" header
+                     + (3 * line_height)                 // 3 action lines
+                     + padding;
     } else {
         // Expanded: calculate based on grid dimensions
         int key_width = (int)(85 * scale);
         int key_height = (int)(45 * scale);
         int grid_padding = (int)(5 * scale);
         int grid_width = 4 * key_width + 3 * grid_padding;
-        // Height: title + mode line + keyboard layout + grid + wheel info + recent actions + padding
+        // Height: title + mode/set line + function pair + grid + history + padding
         int wheel_height = key_height - (int)(10 * scale);
         int grid_height = wheel_height + 5 * (key_height + grid_padding) + grid_padding;
-        int wheel_info_height = 3 * line_height + (int)(10 * scale);  // set indicator + direction
         int history_height = line_height + (int)(5 * scale) + 3 * line_height;  // header + 3 actions
         osd->width = grid_width + 2 * padding;
-        osd->height = title_height + padding + line_height + (int)(10 * scale)
-                     + grid_height + wheel_info_height + history_height + padding;
+        osd->height = title_height + padding
+                     + line_height + (int)(3 * scale)   // mode + set indicator line
+                     + line_height + (int)(8 * scale)   // function pair line
+                     + grid_height
+                     + history_height + padding;
     }
 
     XResizeWindow(dpy, win, osd->width, osd->height);
@@ -589,95 +594,110 @@ void osd_redraw(osd_state_t* osd) {
 
     int y_offset = title_height + padding;
 
-    if (osd->mode == OSD_MODE_MINIMAL) {
-        // -- Mode indicator line --
-        char mode_line[128];
+    // ========== COMMON: Mode line + wheel set indicator + function pair ==========
+    // This block is shared between minimal and expanded to keep wheel info at top
+
+    // -- Mode line with wheel set boxes inline --
+    {
+        char mode_text[64];
         const char* mode_str = osd->wheel.wheel_mode ? "Sets" : "Sequential";
         if (osd->leader_active) {
-            snprintf(mode_line, sizeof(mode_line), "Mode: %s | Leader: ON", mode_str);
+            snprintf(mode_text, sizeof(mode_text), "Mode: %s | Leader: ON", mode_str);
         } else {
-            snprintf(mode_line, sizeof(mode_line), "Mode: %s", mode_str);
+            snprintf(mode_text, sizeof(mode_text), "Mode: %s", mode_str);
         }
         XSetForeground(dpy, gc, dim_color);
-        XDrawString(dpy, win, gc, padding, y_offset, mode_line, strlen(mode_line));
-        y_offset += line_height + (int)(3 * scale);
+        XDrawString(dpy, win, gc, padding, y_offset, mode_text, strlen(mode_text));
 
-        // -- Wheel set + direction info --
+        // Draw wheel set boxes inline after the mode text
         if (osd->wheel.wheel_mode == 1) {
-            // Sets mode: show current set and direction labels
-            int func_idx = osd->wheel.wheel_function;
-            const char* func_desc = NULL;
-            if (func_idx >= 0 && func_idx < 32) {
-                func_desc = osd->wheel.descriptions[func_idx];
+            // Estimate text width (~0.6 * font_size per char)
+            int text_w = (int)(strlen(mode_text) * osd->font_size * 0.6f) + (int)(10 * scale);
+            draw_wheel_set_indicator(dpy, win, gc, osd, padding + text_w, y_offset - (int)(12 * scale), scale);
+        } else {
+            // Sequential mode: draw grayed-out set boxes
+            int text_w = (int)(strlen(mode_text) * osd->font_size * 0.6f) + (int)(10 * scale);
+            int box_w = (int)(30 * scale);
+            int box_h = (int)(18 * scale);
+            int box_gap = (int)(8 * scale);
+            unsigned long grayed = ((unsigned long)100 << 24) | 0x444444;
+            int num_sets = (osd->wheel.total_wheels + 1) / 2;
+            if (num_sets < 1) num_sets = 1;
+            if (num_sets > 3) num_sets = 3;
+            for (int i = 0; i < num_sets; i++) {
+                int bx = padding + text_w + i * (box_w + box_gap);
+                int by = y_offset - (int)(12 * scale);
+                XSetForeground(dpy, gc, grayed);
+                XFillRectangle(dpy, win, gc, bx, by, box_w, box_h);
+                XSetForeground(dpy, gc, ((unsigned long)120 << 24) | 0x666666);
+                XDrawRectangle(dpy, win, gc, bx, by, box_w - 1, box_h - 1);
+                char label[16];
+                snprintf(label, sizeof(label), "%d", i + 1);
+                XSetForeground(dpy, gc, ((unsigned long)120 << 24) | 0x888888);
+                XDrawString(dpy, win, gc, bx + (int)(11 * scale), by + (int)(13 * scale), label, strlen(label));
             }
+        }
+        y_offset += line_height + (int)(3 * scale);
+    }
 
-            char wheel_line[128];
-            if (func_desc) {
-                snprintf(wheel_line, sizeof(wheel_line), "Set %d: %s",
-                         osd->wheel.current_set + 1, func_desc);
-            } else {
-                snprintf(wheel_line, sizeof(wheel_line), "Set %d: Fn %d",
-                         osd->wheel.current_set + 1, func_idx);
-            }
-            XSetForeground(dpy, gc, accent_color);
-            XDrawString(dpy, win, gc, padding, y_offset, wheel_line, strlen(wheel_line));
+    // -- Function pair line: show which function is active in current set --
+    {
+        int pair_idx = osd->wheel.current_set * 2;
+        int pos = osd->wheel.position_in_set;
 
-            // Show both functions in the set
-            int pair_idx = osd->wheel.current_set * 2;
+        if (osd->wheel.wheel_mode == 1) {
+            // Sets mode: show both functions in the pair with > on active
             const char* desc_a = (pair_idx < 32) ? osd->wheel.descriptions[pair_idx] : NULL;
             const char* desc_b = (pair_idx + 1 < 32) ? osd->wheel.descriptions[pair_idx + 1] : NULL;
-            if (desc_a || desc_b) {
-                y_offset += line_height;
-                char pair_line[128];
-                snprintf(pair_line, sizeof(pair_line), "  L: %s | R: %s",
-                         desc_a ? desc_a : "---", desc_b ? desc_b : "---");
-                XSetForeground(dpy, gc, dim_color);
-                XDrawString(dpy, win, gc, padding, y_offset, pair_line, strlen(pair_line));
-            }
+            char fn_a[48], fn_b[48];
+            snprintf(fn_a, sizeof(fn_a), "%s", desc_a ? desc_a : "Fn 0");
+            snprintf(fn_b, sizeof(fn_b), "%s", desc_b ? desc_b : "Fn 1");
+
+            char pair_line[128];
+            snprintf(pair_line, sizeof(pair_line), "Set %d:  %s%s  |  %s%s",
+                     osd->wheel.current_set + 1,
+                     pos == 0 ? "> " : "  ", fn_a,
+                     pos == 1 ? "> " : "  ", fn_b);
+            XSetForeground(dpy, gc, accent_color);
+            XDrawString(dpy, win, gc, padding, y_offset, pair_line, strlen(pair_line));
         } else {
             // Sequential mode: show current wheel function
             int func_idx = osd->wheel.wheel_function;
             const char* func_desc = (func_idx >= 0 && func_idx < 32) ?
                                      osd->wheel.descriptions[func_idx] : NULL;
-            char wheel_line[128];
+            char seq_line[128];
             if (func_desc) {
-                snprintf(wheel_line, sizeof(wheel_line), "Wheel: %s (Fn %d)", func_desc, func_idx);
+                snprintf(seq_line, sizeof(seq_line), "Wheel: %s (Fn %d/%d)",
+                         func_desc, func_idx + 1, osd->wheel.total_wheels);
             } else {
-                snprintf(wheel_line, sizeof(wheel_line), "Wheel: Fn %d", func_idx);
+                snprintf(seq_line, sizeof(seq_line), "Wheel: Fn %d/%d",
+                         func_idx + 1, osd->wheel.total_wheels);
             }
             XSetForeground(dpy, gc, accent_color);
-            XDrawString(dpy, win, gc, padding, y_offset, wheel_line, strlen(wheel_line));
+            XDrawString(dpy, win, gc, padding, y_offset, seq_line, strlen(seq_line));
         }
-        y_offset += line_height + (int)(5 * scale);
+        y_offset += line_height + (int)(3 * scale);
+    }
+
+    if (osd->mode == OSD_MODE_MINIMAL) {
+        // ========== MINIMAL MODE ==========
 
         // -- Recent Actions --
         XSetForeground(dpy, gc, fg_color);
         XDrawString(dpy, win, gc, padding, y_offset, "Recent Actions:", 15);
-        y_offset += line_height + (int)(3 * scale);
+        y_offset += line_height + (int)(5 * scale);
 
         draw_recent_actions(dpy, win, gc, osd, padding, y_offset, 3, scale);
 
     } else {
         // ========== EXPANDED MODE ==========
 
-        // -- Mode indicator line --
-        char mode_line[128];
-        const char* mode_str = osd->wheel.wheel_mode ? "Sets" : "Sequential";
-        if (osd->leader_active) {
-            snprintf(mode_line, sizeof(mode_line), "Mode: %s | Leader: ON", mode_str);
-        } else {
-            snprintf(mode_line, sizeof(mode_line), "Mode: %s", mode_str);
-        }
-        XSetForeground(dpy, gc, dim_color);
-        XDrawString(dpy, win, gc, padding, y_offset, mode_line, strlen(mode_line));
-        y_offset += line_height + (int)(10 * scale);
-
         // -- Keyboard grid --
         int key_width = (int)(85 * scale);
         int key_height = (int)(45 * scale);
         int grid_padding = (int)(5 * scale);
         int start_x = padding;
-        int start_y = y_offset;
+        int start_y = y_offset + (int)(5 * scale);
         int grid_width = 4 * key_width + 3 * grid_padding;
         int wheel_height = key_height - (int)(10 * scale);
 
@@ -728,76 +748,6 @@ void osd_redraw(osd_state_t* osd) {
 
         y_offset = bottom_y + key_height + (int)(10 * scale);
 
-        // -- Wheel Set Indicator --
-        XSetForeground(dpy, gc, fg_color);
-        const char* wheel_label = "Wheel Sets:";
-        XDrawString(dpy, win, gc, padding, y_offset, wheel_label, strlen(wheel_label));
-
-        // Draw set boxes next to label
-        int label_width = (int)(80 * scale);
-        draw_wheel_set_indicator(dpy, win, gc, osd, padding + label_width, y_offset - (int)(12 * scale), scale);
-        y_offset += line_height + (int)(3 * scale);
-
-        // -- Wheel direction labels for current set --
-        if (osd->wheel.wheel_mode == 1) {
-            int pair_idx = osd->wheel.current_set * 2;
-            const char* desc_a = (pair_idx < 32) ? osd->wheel.descriptions[pair_idx] : NULL;
-            const char* desc_b = (pair_idx + 1 < 32) ? osd->wheel.descriptions[pair_idx + 1] : NULL;
-
-            char dir_line[128];
-            // Position 0 = active highlighted
-            int pos = osd->wheel.position_in_set;
-            if (desc_a && desc_b) {
-                snprintf(dir_line, sizeof(dir_line), "%s%s  |  %s%s",
-                         pos == 0 ? "> " : "  ", desc_a,
-                         pos == 1 ? "> " : "  ", desc_b);
-            } else if (desc_a) {
-                snprintf(dir_line, sizeof(dir_line), "%s%s  |  ---",
-                         pos == 0 ? "> " : "  ", desc_a);
-            } else if (desc_b) {
-                snprintf(dir_line, sizeof(dir_line), "---  |  %s%s",
-                         pos == 1 ? "> " : "  ", desc_b);
-            } else {
-                snprintf(dir_line, sizeof(dir_line), "Fn %d  |  Fn %d", pair_idx, pair_idx + 1);
-            }
-            XSetForeground(dpy, gc, accent_color);
-            XDrawString(dpy, win, gc, padding, y_offset, dir_line, strlen(dir_line));
-            y_offset += line_height;
-
-            // Show last wheel action (aggregated)
-            if (osd->wheel.last_wheel_action && (now - osd->wheel.last_wheel_time_ms) < osd->display_duration_ms) {
-                XSetForeground(dpy, gc, dim_color);
-                XDrawString(dpy, win, gc, padding, y_offset,
-                            osd->wheel.last_wheel_action, strlen(osd->wheel.last_wheel_action));
-            }
-            y_offset += line_height;
-        } else {
-            // Sequential mode
-            int func_idx = osd->wheel.wheel_function;
-            const char* func_desc = (func_idx >= 0 && func_idx < 32) ?
-                                     osd->wheel.descriptions[func_idx] : NULL;
-            char seq_line[128];
-            if (func_desc) {
-                snprintf(seq_line, sizeof(seq_line), "Active: %s (Fn %d/%d)",
-                         func_desc, func_idx, osd->wheel.total_wheels);
-            } else {
-                snprintf(seq_line, sizeof(seq_line), "Active: Fn %d/%d",
-                         func_idx, osd->wheel.total_wheels);
-            }
-            XSetForeground(dpy, gc, accent_color);
-            XDrawString(dpy, win, gc, padding, y_offset, seq_line, strlen(seq_line));
-            y_offset += line_height;
-
-            if (osd->wheel.last_wheel_action && (now - osd->wheel.last_wheel_time_ms) < osd->display_duration_ms) {
-                XSetForeground(dpy, gc, dim_color);
-                XDrawString(dpy, win, gc, padding, y_offset,
-                            osd->wheel.last_wheel_action, strlen(osd->wheel.last_wheel_action));
-            }
-            y_offset += line_height;
-        }
-
-        y_offset += (int)(5 * scale);
-
         // -- Recent Actions (3 items in expanded view too) --
         XSetForeground(dpy, gc, fg_color);
         XDrawString(dpy, win, gc, padding, y_offset, "Recent Actions:", 15);