···259259260260 #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
261261 pub fn draw(&mut self, fb: &mut crate::gfx::framebuffer::Framebuffer, area: Area) {
262262- let gutter_w = 24i32;
262262+ // Gutter width: 3 digits * 6px = 18px, plus a 1px gap before code
263263+ let gutter_w = 18i32;
264264+ let gap = 1i32;
263265 // Match TIC-80 editor line pitch: 7px (TIC_FONT_HEIGHT + 1)
264266 let line_pitch = 7i32;
265267 let lines_vis = (area.h / line_pitch).max(1) as usize;
266266- let cols_vis = ((area.w - gutter_w) / 6).max(1) as usize;
268268+ let cols_vis = ((area.w - gutter_w - gap) / 6).max(1) as usize;
267269 self.ensure_visible(lines_vis, cols_vis);
268270269271 // Clip to drawing area
···279281 let gutter_y = area.y + i32::try_from(i).unwrap_or(0) * line_pitch;
280282 let ln = line_idx + 1;
281283 let label = format!("{ln:>3}");
282282- let _ = fb.print_text(&label, area.x + 2, gutter_y, 6, true, 1, false);
284284+ // Right-justified 3-digit label, drawn flush to gutter (no extra left/right padding)
285285+ let _ = fb.print_text(&label, area.x, gutter_y, 14, true, 1, true);
283286284287 // Text slice
285288 let mut line = self.rope.line(line_idx).to_string();
···293296 let line_char_start = self.rope.line_to_char(line_idx);
294297 let sel = self.selection_range_idx();
295298 for (i_vis, ch) in vis.chars().enumerate() {
296296- let cell_x = area.x + gutter_w + i32::try_from(i_vis).unwrap_or(0) * 6;
299299+ let cell_x = area.x + gutter_w + gap + i32::try_from(i_vis).unwrap_or(0) * 6;
297300 let cell_y = gutter_y;
298301 let global_idx = line_char_start + start + i_vis;
299302 let selected = sel.is_some_and(|(s, e)| global_idx >= s && global_idx < e);
···304307 fb.rect(cell_x - 1, cell_y - 1, 7, 7, 14);
305308 // Dark glyph on top
306309 let s = ch.to_string();
307307- let _ = fb.print_text(&s, cell_x, cell_y, 15, true, 1, false);
310310+ let _ = fb.print_text(&s, cell_x, cell_y, 15, true, 1, true);
308311 } else {
309312 // Normal glyph (no selection overlay)
310313 let s = ch.to_string();
311311- // TIC default text color (no syntax) is white (12)
312312- let _ = fb.print_text(&s, cell_x, cell_y, 12, true, 1, false);
314314+ // TIC default text color (no syntax) is white (12), 6px tall
315315+ let _ = fb.print_text(&s, cell_x, cell_y, 12, true, 1, true);
313316 }
314317 }
315318 }
···318321 if self.caret_line >= self.scroll_line && self.caret_line < self.scroll_line + lines_vis {
319322 let row = i32::try_from(self.caret_line - self.scroll_line).unwrap_or(0);
320323 let col = i32::try_from(self.caret_col.saturating_sub(self.scroll_col)).unwrap_or(0);
321321- let cell_x = area.x + gutter_w + col * 6;
324324+ let cell_x = area.x + gutter_w + gap + col * 6;
322325 let cell_y = area.y + row * line_pitch;
323326 // TIC-80 caret style: drop shadow rect (black) then caret rect (cursor color, default 2), both 7x7, offset by 1px
324327 fb.rect(cell_x, cell_y, 7, 7, 0);
···338341 let ch = full.chars().nth(idx).unwrap_or(' ');
339342 let s = ch.to_string();
340343 // Render underlying glyph in background color to simulate inversion
341341- let _ = fb.print_text(&s, cell_x, cell_y, 15, true, 1, false);
344344+ let _ = fb.print_text(&s, cell_x, cell_y, 15, true, 1, true);
342345 }
343346 }
344347 }
+2-9
tic80_rust/src/editor/ui.rs
···133133 // TIC-80 draws toolbar in white
134134 fb.rect(0, 0, 240, bar_h, 12);
135135136136- // Tabs (underline only for CODE)
137137- let (code_col, _cons_col) = match self.active {
138138- // Use grey underline similar to CODE EDITOR text
139139- Tab::Code | Tab::Console => (14, 14),
140140- };
141141- // Minimal underline only for CODE
142142- fb.rect(self.tab_code.x, bar_h - 1, self.tab_code.w, 1, code_col);
136136+ // No tab underline for now; only the CODE label at top-left.
143137 // Buttons
144138 // Buttons: skip heavy boxes in code prototype
145139146140 // Labels (using small scale)
147147- // Left title label: drop shadow 1px (dark grey 15) then grey (14), like "CODE EDITOR"
141141+ // Left title label: grey text only (no shadow for now)
148142 let title_x = 4;
149143 let title_y = 1; // 1px top margin
150150- let _ = fb.print_text("CODE", title_x + 1, title_y + 1, 15, true, 1, true);
151144 let _ = fb.print_text("CODE", title_x, title_y, 14, true, 1, true);
152145 // Center button labels (placeholder, keep white for readability)
153146 let adv = 6i32;
+2-7
tic80_rust/src/main.rs
···611611 ui.draw(&mut fbb);
612612 if ui.active == tic80_rust::editor::ui::Tab::Code {
613613 if let Some(cb) = code_buf.as_mut() {
614614- let area = CodeArea { x: 0, y: 12, w: 240, h: 124 };
614614+ let area = CodeArea { x: 0, y: 7, w: 240, h: 129 };
615615 cb.draw(&mut fbb, area);
616616 }
617617 }
···713713 {
714714 let mut fbb = fb.borrow_mut();
715715 ui.draw(&mut fbb);
716716- let area = CodeArea {
717717- x: 0,
718718- y: 12,
719719- w: 240,
720720- h: 124,
721721- };
716716+ let area = CodeArea { x: 0, y: 7, w: 240, h: 129 };
722717 code.draw(&mut fbb, area);
723718 }
724719 } else {
+7-12
tic80_rust/tests/editor_code_view_tests.rs
···1010 let mut cb = CodeBuffer::from_text(text);
1111 let fb = Rc::new(RefCell::new(Framebuffer::new()));
1212 let mut fbb = fb.borrow_mut();
1313- let area = Area {
1414- x: 0,
1515- y: 12,
1616- w: 240,
1717- h: 40,
1818- };
1313+ let area = Area { x: 0, y: 7, w: 240, h: 40 };
1914 cb.draw(&mut fbb, area);
2020- // Expect some non-zero pixels in gutter (left side)
1515+ // Expect some non-zero pixels in gutter (left side, 0..18)
2116 let mut gutter_ink = 0;
2222- for y in 12..20 {
2323- for x in 2..22 {
1717+ for y in 7..14 {
1818+ for x in 0..18 {
2419 if fbb.pix(x, y, None).unwrap_or(0) != 0 {
2520 gutter_ink += 1;
2621 }
2722 }
2823 }
2924 assert!(gutter_ink > 0);
3030- // Expect some text pixels in the first line area after gutter (x >= 24)
2525+ // Expect some text pixels in the first line area after gutter+gap (x >= 19)
3126 let mut line_ink = 0;
3232- for x in 24..60 {
3333- if fbb.pix(x, 12, None).unwrap_or(0) != 0 {
2727+ for x in 19..60 {
2828+ if fbb.pix(x, 7, None).unwrap_or(0) != 0 {
3429 line_ink += 1;
3530 }
3631 }
+6-15
tic80_rust/tests/editor_selection_shadow_tests.rs
···24242525 let fb_rc = Rc::new(RefCell::new(Framebuffer::new()));
2626 let mut fb = fb_rc.borrow_mut();
2727- let area = Area {
2828- x: 0,
2929- y: 12,
3030- w: 240,
3131- h: 40,
3232- };
2727+ let area = Area { x: 0, y: 7, w: 240, h: 40 };
3328 cb.draw(&mut fb, area);
34293530 // Pick column 1 (the 'b') well inside selection run
3636- let gutter_w = 24i32;
3131+ let gutter_w = 18i32; // plus 1px gap in renderer
3732 let col_x = gutter_w + 6 + 2; // inside col #1
38333934 // With TIC-80 logic there should be NO black seam between consecutive lines.
···60556156 let fb_rc = Rc::new(RefCell::new(Framebuffer::new()));
6257 let mut fb = fb_rc.borrow_mut();
6363- let area = Area {
6464- x: 0,
6565- y: 12,
6666- w: 240,
6767- h: 20,
6868- };
5858+ let area = Area { x: 0, y: 7, w: 240, h: 20 };
6959 cb.draw(&mut fb, area);
70607171- let gutter_w = 24i32;
7272- let right_edge_x = gutter_w + 3 * 6 - 1; // vertical shadow at right edge of col2
6161+ let gutter_w = 18i32; // plus 1px gap
6262+ let gap = 1i32;
6363+ let right_edge_x = gutter_w + gap + 3 * 6 - 1; // vertical shadow at right edge of col2
7364 let base_y = area.y - 1; // selection fill starts at y-1; shadow spans 7 px down from y
74657566 let mut black_count = 0;