commit 652aa711a24dc3dc62cd21bffb0e5869248c90bc
Author: Nedko Arnaudov <nedko@nedk.org>
Date:   Mon Mar 30 03:31:22 2026 +0300

    Implement initial UI zones and inline layout math
    
    - Remove layout macros (FX/FY) in favor of inline fraction
      literals, exposing proportional math directly in each zone
      function for clarity and easier layout adjustments
    - Introduce zero_group_spacing() helper to disable default
      Nuklear padding/spacing, matching the original FT2 gapless
      aesthetic
    - Replace colored-background stub zones with functional widgets:
      two-label status bar, left menu (11 buttons with Add/Sub
      split row), and right menu (10 buttons)
    - Refactor zone functions to single-line compound initializers
      and standardize fraction naming in comments (frac_x etc.)

diff --git a/src/nk.c b/src/nk.c
index eb93c73..9f02b4f 100644
--- a/src/nk.c
+++ b/src/nk.c
@@ -27,211 +27,241 @@
 /* ------------------------------------------------------------------ */
 /*  Proportional zone descriptor                                      */
 /*                                                                     */
-/*  Each zone is expressed as (x_frac, y_frac, w_frac, h_frac) where  */
-/*  fractions are of the *base* 632x400 layout, so the actual pixel   */
+/*  Each zone is expressed as (frac_x, frac_y, frac_w, frac_h) where */
+/*  fractions are of the *base* 632x400 layout.  The actual pixel     */
 /*  rect for a given window size WxH is:                              */
 /*                                                                     */
-/*    x = x_frac * W     y = y_frac * H                              */
-/*    w = w_frac * W     h = h_frac * H                              */
+/*    x = frac_x * W     y = frac_y * H                              */
+/*    w = frac_w * W     h = frac_h * H                              */
 /*                                                                     */
-/*  Base pixel values from original source are kept as comments.      */
+/*  All fractions are trivially derivable from the integer pixel      */
+/*  coords in the original source - see comments below.               */
 /* ------------------------------------------------------------------ */
 
 typedef struct { float x, y, w, h; } zone_rect;
 
-/* Base layout size (FT2 normal mode) */
-#define BASE_W 632.0f
-#define BASE_H 400.0f
-
-/* Helper macro: px/632 */
-#define FX(px) ((float)(px) / BASE_W)
-/* Helper macro: px/400 */
-#define FY(px) ((float)(px) / BASE_H)
+/* ------------------------------------------------------------------ */
+/*  Zone fractions (precomputed from base 632x400 pixel coords)       */
+/*                                                                     */
+/*  Each zone is commented with its original pixel rectangle so the   */
+/*  fractions can be audited:  frac = px / 632  (or / 400 for y/h).   */
+/* ------------------------------------------------------------------ */
 
 /*
- * Top-left block: x=0..291, y=0..173  (291x173 px)
+ * Top-left block: x=0..291, y=0..173
  *
- *   Position Editor :  0,   0, 112,  77
- *   Logo            : 112,  0, 179,  32
- *   BPM/Spd/Add     : 112, 32,  94,  45
- *   Ptn/Ln          : 206, 32,  85,  45
- *   Status Bar      :  0,  77, 291,  15
- *   Scopes          :  0,  92, 291,  81
+ *   Position Editor :  0,   0, 112,  77   -> (0.0, 0.0,   112/632, 77/400)
+ *   Logo            : 112,  0, 179,  32   -> (112/632, 0.0, 179/632, 32/400)
+ *   BPM/Spd/Add     : 112, 32,  94,  45   -> (112/632, 32/400, 94/632, 45/400)
+ *   Ptn/Ln          : 206, 32,  85,  45   -> (206/632, 32/400, 85/632, 45/400)
+ *   Status Bar      :  0,  77, 291,  15   -> (0.0, 77/400, 291/632, 15/400)
+ *   Scopes          :  0,  92, 291,  81   -> (0.0, 92/400, 291/632, 81/400)
  *
- * Right-menu column: x=291..421, y=0..173  (130x173 px)
+ * Right-menu column: x=291..421
  *
- *   Left Menu  : 291, 0,  65, 173
- *   Right Menu : 356, 0,  65, 173
+ *   Left Menu  : 291, 0,  65, 173        -> (291/632, 0.0, 65/632, 173/400)
+ *   Right Menu : 356, 0,  65, 173        -> (356/632, 0.0, 65/632, 173/400)
  *
- * Instrument switcher: x=421..632, y=0..173  (211x173 px)
+ * Instrument switcher: x=421..632
+ *
+ *   Inst Switcher : 421, 0, 211, 173     -> (421/632, 0.0, 211/632, 173/400)
  *
  * Bottom:
  *
- *   Pattern Editor : 0, 173, 632, 210
- *   Chan Scroll    : 0, 383, 632,  17
+ *   Pattern Editor : 0, 173, 632, 210     -> (0.0, 173/400, 1.0, 210/400)
+ *   Chan Scroll    : 0, 383, 632,  17     -> (0.0, 383/400, 1.0, 17/400)
  */
 
-/* ---- top-left block proportions (of 291x173) ---- */
-#define TL_X  FX(0)
-#define TL_Y  FY(0)
-#define TL_W  FX(291)
-#define TL_H  FY(173)
-
-static zone_rect
-zone_pos_ed(float ww, float wh)
+static zone_rect zone_pos_ed(float ww, float wh)
 {
-    zone_rect r;
-    r.x = TL_X * ww;                 /* 0 */
-    r.y = TL_Y * wh;                 /* 0 */
-    r.w = FX(112) * ww;              /* 112/632 of window width */
-    r.h = FY(77)  * wh;              /* 77/400 of window height */
+    zone_rect r = { 0.0f, 0.0f, (112.0f/632.0f)*ww, (77.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_logo(float ww, float wh)
+static zone_rect zone_logo(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(112) * ww;
-    r.y = FY(0)   * wh;
-    r.w = FX(179) * ww;
-    r.h = FY(32)  * wh;
+    zone_rect r = { (112.0f/632.0f)*ww, 0.0f, (179.0f/632.0f)*ww, (32.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_bpm_spd(float ww, float wh)
+static zone_rect zone_bpm_spd(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(112) * ww;
-    r.y = FY(32)  * wh;
-    r.w = FX(94)  * ww;
-    r.h = FY(45)  * wh;
+    zone_rect r = { (112.0f/632.0f)*ww, (32.0f/400.0f)*wh, (94.0f/632.0f)*ww, (45.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_ptn_ln(float ww, float wh)
+static zone_rect zone_ptn_ln(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(206) * ww;
-    r.y = FY(32)  * wh;
-    r.w = FX(85)  * ww;
-    r.h = FY(45)  * wh;
+    zone_rect r = { (206.0f/632.0f)*ww, (32.0f/400.0f)*wh, (85.0f/632.0f)*ww, (45.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_status_bar(float ww, float wh)
+static zone_rect zone_status_bar(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(0)   * ww;
-    r.y = FY(77)  * wh;
-    r.w = FX(291) * ww;
-    r.h = FY(15)  * wh;
+    zone_rect r = { 0.0f, (77.0f/400.0f)*wh, (291.0f/632.0f)*ww, (15.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_scopes(float ww, float wh)
+static zone_rect zone_scopes(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(0)   * ww;
-    r.y = FY(92)  * wh;
-    r.w = FX(291) * ww;
-    r.h = FY(81)  * wh;
+    zone_rect r = { 0.0f, (92.0f/400.0f)*wh, (291.0f/632.0f)*ww, (81.0f/400.0f)*wh };
     return r;
 }
 
-/* ---- right-menu column proportions (of 130x173) ---- */
-
-static zone_rect
-zone_left_menu(float ww, float wh)
+static zone_rect zone_left_menu(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(291) * ww;
-    r.y = FY(0)   * wh;
-    r.w = FX(65)  * ww;
-    r.h = FY(173) * wh;
+    zone_rect r = { (291.0f/632.0f)*ww, 0.0f, (65.0f/632.0f)*ww, (173.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_right_menu(float ww, float wh)
+static zone_rect zone_right_menu(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(356) * ww;
-    r.y = FY(0)   * wh;
-    r.w = FX(65)  * ww;
-    r.h = FY(173) * wh;
+    zone_rect r = { (356.0f/632.0f)*ww, 0.0f, (65.0f/632.0f)*ww, (173.0f/400.0f)*wh };
     return r;
 }
 
-/* ---- instrument switcher proportions ---- */
-
-static zone_rect
-zone_inst_switcher(float ww, float wh)
+static zone_rect zone_inst_switcher(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(421) * ww;
-    r.y = FY(0)   * wh;
-    r.w = FX(211) * ww;
-    r.h = FY(173) * wh;
+    zone_rect r = { (421.0f/632.0f)*ww, 0.0f, (211.0f/632.0f)*ww, (173.0f/400.0f)*wh };
     return r;
 }
 
-/* ---- bottom proportions ---- */
-
-static zone_rect
-zone_pattern_editor(float ww, float wh)
+static zone_rect zone_pattern_editor(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(0)   * ww;
-    r.y = FY(173) * wh;
-    r.w = FX(632) * ww;
-    r.h = FY(210) * wh;
+    zone_rect r = { 0.0f, (173.0f/400.0f)*wh, ww, (210.0f/400.0f)*wh };
     return r;
 }
 
-static zone_rect
-zone_chan_scroll(float ww, float wh)
+static zone_rect zone_chan_scroll(float ww, float wh)
 {
-    zone_rect r;
-    r.x = FX(0)   * ww;
-    r.y = FY(383) * wh;
-    r.w = FX(632) * ww;
-    r.h = FY(17)  * wh;
+    zone_rect r = { 0.0f, (383.0f/400.0f)*wh, ww, (17.0f/400.0f)*wh };
     return r;
 }
 
+/* ------------------------------------------------------------------ */
+/*  Stub zone (bordered rectangle with label)                         */
 /* ------------------------------------------------------------------ */
 
-/* Helper to sketch a named zone with a distinct color */
 static void
-draw_zone(struct nk_context *ctx, const char *name, zone_rect r, struct nk_color bg)
+draw_zone(struct nk_context *ctx, const char *name, zone_rect r, int ci)
 {
-    struct nk_style_item old_bg = ctx->style.window.fixed_background;
-    struct nk_color old_border_color = ctx->style.window.border_color;
+    (void)ci;
+    nk_layout_space_push(ctx, nk_rect(r.x, r.y, r.w, r.h));
+    if (nk_group_begin(ctx, name, NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER))
+    {
+        nk_layout_row_dynamic(ctx, r.h, 1);
+        nk_label(ctx, name, NK_TEXT_CENTERED);
+        nk_group_end(ctx);
+    }
+}
+
+/* ------------------------------------------------------------------ */
+/*  Helper: zero out spacing/padding so buttons fill zone width       */
+/* ------------------------------------------------------------------ */
 
-    ctx->style.window.fixed_background = nk_style_item_color(bg);
-    ctx->style.window.border_color = nk_rgb(90, 90, 90);
+static void
+zero_group_spacing(struct nk_context *ctx)
+{
+    ctx->style.window.spacing       = nk_vec2(0, 0);
+    ctx->style.window.padding       = nk_vec2(0, 0);
+    ctx->style.window.group_padding = nk_vec2(0, 0);
+}
 
+/*
+ * Status bar - original ft2_gui.c line 1073/1078:
+ *   "Global volume" on the left, "Time" on the right.
+ *   291x15 px base.
+ */
+static void
+draw_status_bar(struct nk_context *ctx, zone_rect r)
+{
     nk_layout_space_push(ctx, nk_rect(r.x, r.y, r.w, r.h));
-    if (nk_group_begin(ctx, name, NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER)) {
-        nk_layout_row_dynamic(ctx, r.h - 2.0f, 1);
+    if (nk_group_begin(ctx, "StatusBar", NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER))
+    {
+        zero_group_spacing(ctx);
 
-        struct nk_color old_text = ctx->style.text.color;
-        ctx->style.text.color = nk_rgb(220, 220, 220);
-        nk_label(ctx, name, NK_TEXT_CENTERED);
-        ctx->style.text.color = old_text;
+        nk_layout_row_template_begin(ctx, r.h);
+        nk_layout_row_template_push_dynamic(ctx);
+        nk_layout_row_template_push_dynamic(ctx);
+        nk_layout_row_template_end(ctx);
+
+        nk_label(ctx, "Global volume: 64", NK_TEXT_LEFT);
+        nk_label(ctx, "Time: 00:00:00", NK_TEXT_RIGHT);
 
         nk_group_end(ctx);
     }
+}
 
-    ctx->style.window.fixed_background = old_bg;
-    ctx->style.window.border_color = old_border_color;
+/*
+ * Left menu - original ft2_pushbuttons.c lines 91-101:
+ *   11 buttons at x=294, y=2..155, w=59 h=16 each (last row: Add+Sub side by side).
+ *   Labels: About, Nibbles, Zap, Trim, Extend, Transps., I.E.Ext., S.E.Ext., Adv. Edit, Add, Sub
+ *
+ * 10 rows, last row has Add/Sub at 50/50 split.
+ */
+static void
+draw_left_menu(struct nk_context *ctx, zone_rect r)
+{
+    nk_layout_space_push(ctx, nk_rect(r.x, r.y, r.w, r.h));
+    if (nk_group_begin(ctx, "LeftMenu", NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER))
+    {
+        const char *labels[] = {
+            "About", "Nibbles", "Zap", "Trim", "Extend",
+            "Transps.", "I.E.Ext.", "S.E.Ext.", "Adv. Edit",
+            NULL  /* last row is Add/Sub */
+        };
+        const int num_full = 9; /* rows 0..8: single buttons */
+        const float bh = r.h / 10.0f; /* 10 rows, last row has Add/Sub split */
+
+        zero_group_spacing(ctx);
+
+        for (int i = 0; i < num_full; i++) {
+            nk_layout_row_dynamic(ctx, bh, 1);
+            nk_button_label(ctx, labels[i]);
+        }
+        /* Last row: Add + Sub side by side */
+        nk_layout_row_dynamic(ctx, bh, 2);
+        nk_button_label(ctx, "Add");
+        nk_button_label(ctx, "Sub");
+
+        nk_group_end(ctx);
+    }
 }
 
+/*
+ * Right menu - original ft2_pushbuttons.c lines 102-111:
+ *   10 buttons at x=359, y=2..155, w=59 h=16 each.
+ *   Labels: Play sng., Play ptn., Stop, Rec. sng., Rec. ptn.,
+ *           Disk op., Instr. Ed., Smp. Ed., Config, Help
+ */
+static void
+draw_right_menu(struct nk_context *ctx, zone_rect r)
+{
+    nk_layout_space_push(ctx, nk_rect(r.x, r.y, r.w, r.h));
+    if (nk_group_begin(ctx, "RightMenu", NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER))
+    {
+        const char *labels[] = {
+            "Play sng.", "Play ptn.", "Stop", "Rec. sng.", "Rec. ptn.",
+            "Disk op.", "Instr. Ed.", "Smp. Ed.", "Config", "Help"
+        };
+        const int num = 10;
+        const float bh = r.h / (float)num;
+
+        zero_group_spacing(ctx);
+
+        nk_layout_row_dynamic(ctx, bh, 1);
+        for (int i = 0; i < num; i++) {
+            nk_button_label(ctx, labels[i]);
+        }
+
+        nk_group_end(ctx);
+    }
+}
+
+/* ------------------------------------------------------------------ */
+/*  Main refresh callback                                             */
+/* ------------------------------------------------------------------ */
+
 static void
 on_nk_pugl_refresh(
     struct nk_context *ctx,
@@ -257,23 +287,23 @@ on_nk_pugl_refresh(
         nk_layout_space_begin(ctx, NK_STATIC, wh, 11);
 
         /* === TOP-LEFT === */
-        draw_zone(ctx, "Position Editor",  zone_pos_ed(ww, wh),       nk_rgb(35, 40, 50));
-        draw_zone(ctx, "Logo",             zone_logo(ww, wh),          nk_rgb(45, 45, 55));
-        draw_zone(ctx, "BPM/Spd/Add",      zone_bpm_spd(ww, wh),      nk_rgb(40, 45, 55));
-        draw_zone(ctx, "Ptn/Ln/Expd/Srnk", zone_ptn_ln(ww, wh),      nk_rgb(38, 42, 52));
-        draw_zone(ctx, "Status Bar",        zone_status_bar(ww, wh),   nk_rgb(50, 55, 65));
-        draw_zone(ctx, "Scopes",            zone_scopes(ww, wh),       nk_rgb(20, 22, 28));
+        draw_zone(ctx, "Position Editor",  zone_pos_ed(ww, wh), 0);
+        draw_zone(ctx, "Logo",             zone_logo(ww, wh), 1);
+        draw_zone(ctx, "BPM/Spd/Add",      zone_bpm_spd(ww, wh), 2);
+        draw_zone(ctx, "Ptn/Ln/Expd/Srnk", zone_ptn_ln(ww, wh), 3);
+
+        draw_status_bar(ctx, zone_status_bar(ww, wh));
+        draw_zone(ctx, "Scopes",           zone_scopes(ww, wh), 4);
 
-        /* === MENUS === */
-        draw_zone(ctx, "Left Menu",         zone_left_menu(ww, wh),    nk_rgb(30, 35, 45));
-        draw_zone(ctx, "Right Menu",        zone_right_menu(ww, wh),   nk_rgb(35, 40, 50));
+        draw_left_menu(ctx, zone_left_menu(ww, wh));
+        draw_right_menu(ctx, zone_right_menu(ww, wh));
 
         /* === INSTRUMENT SWITCHER === */
-        draw_zone(ctx, "Inst Switcher",     zone_inst_switcher(ww, wh), nk_rgb(45, 50, 60));
+        draw_zone(ctx, "Inst Switcher",    zone_inst_switcher(ww, wh), 5);
 
         /* === BOTTOM === */
-        draw_zone(ctx, "Pattern Editor",    zone_pattern_editor(ww, wh), nk_rgb(15, 18, 22));
-        draw_zone(ctx, "Chan Scroll",       zone_chan_scroll(ww, wh),   nk_rgb(60, 65, 75));
+        draw_zone(ctx, "Pattern Editor",   zone_pattern_editor(ww, wh), 6);
+        draw_zone(ctx, "Chan Scroll",      zone_chan_scroll(ww, wh), 7);
 
         nk_layout_space_end(ctx);
     }
