Editor layout fixes from a visual check

No fog while painting, readable tool bars, a two-row spawn block, a
dialog that keeps the clicks to itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 17:46:51 +01:00
1 parent abe01ff23e
commit aacc6a00b2
3 files changed
+77 -41

No files matched your search

+23 -9
View File
@@ -169,7 +169,6 @@ impl BlockEdit {
if im.button(gfx, Rect::new(CW(), CH() * 0.7, CW() * 12.0, CH() * 1.7), &tr("ed.back")) || (is_key_pressed(KeyCode::Escape) && !im.typing && self.drag.is_none()) { if im.button(gfx, Rect::new(CW(), CH() * 0.7, CW() * 12.0, CH() * 1.7), &tr("ed.back")) || (is_key_pressed(KeyCode::Escape) && !im.typing && self.drag.is_none()) {
back = true; back = true;
} }
gfx.text(&title, CW() * 14.0, CH() * 1.0, S(), FRAME_HI);
let mut bx = w - CW(); let mut bx = w - CW();
let buttons = [tr("ed.delete"), tr("ed.paste"), tr("ed.copy"), tr("ed.redo"), tr("ed.undo")]; let buttons = [tr("ed.delete"), tr("ed.paste"), tr("ed.copy"), tr("ed.redo"), tr("ed.undo")];
let mut pressed_btn = None; let mut pressed_btn = None;
@@ -184,6 +183,10 @@ impl BlockEdit {
let sw_ = CW() * 18.0; let sw_ = CW() * 18.0;
bx -= sw_ + CW(); bx -= sw_ + CW();
im.text_field(gfx, id("bsearch", 0), Rect::new(bx, CH() * 0.7, sw_, CH() * 1.7), &mut self.search, 24); im.text_field(gfx, id("bsearch", 0), Rect::new(bx, CH() * 0.7, sw_, CH() * 1.7), &mut self.search, 24);
// the script's name, as much as fits before the search field
let room = ((bx - CW() * 15.0) / CW()).max(4.0) as usize;
let title: String = title.chars().take(room).collect();
gfx.text(&title, CW() * 14.0, CH() * 1.0, S(), FRAME_HI);
if self.search.is_empty() && im.focus != Some(id("bsearch", 0)) { if self.search.is_empty() && im.focus != Some(id("bsearch", 0)) {
gfx.text(&tr("ed.search"), bx + CW() * 0.5, CH() * 1.0, S(), DIM); gfx.text(&tr("ed.search"), bx + CW() * 0.5, CH() * 1.0, S(), DIM);
} }
@@ -294,7 +297,9 @@ impl BlockEdit {
} }
y += CH() * 2.2; y += CH() * 2.2;
} }
gfx.text(&tr("ed.blocks_hint"), CW() * 0.5, h - CH() * 6.0, SMALL(), DIM); for (k, l) in wrap(&tr("ed.blocks_hint"), ((pal_w - CW()) / (GW * SMALL())) as usize).iter().enumerate() {
gfx.text(l, CW() * 0.5, h - CH() * 6.0 + k as f32 * GH * SMALL() * 1.2, SMALL(), DIM);
}
// ---- the script // ---- the script
let ws = Rect::new(pal_w, top, w - pal_w, h - top - CH() * 2.0); let ws = Rect::new(pal_w, top, w - pal_w, h - top - CH() * 2.0);
@@ -418,13 +423,15 @@ impl BlockEdit {
path.push(k); path.push(k);
let cat = list[k].category(); let cat = list[k].category();
let col = cat_color(cat); let col = cat_color(cat);
let header = Rect::new(x, y, w, CH() * ROW); // the spawn block has many fields: two rows
let rows = if matches!(list[k], Block::Spawn { .. }) { 2.0 } else { 1.0 };
let header = Rect::new(x, y, w, CH() * ROW * rows);
let problem = ctx.problems.iter().find(|(p, _)| *p == path).map(|(_, k)| *k); let problem = ctx.problems.iter().find(|(p, _)| *p == path).map(|(_, k)| *k);
let selected = self.selected.as_ref() == Some(&path); let selected = self.selected.as_ref() == Some(&path);
let found = !ctx.search.is_empty() && (tr(&format!("blk.{}", list[k].kind_name())).to_lowercase().contains(&ctx.search) || block_text(&list[k]).to_lowercase().contains(&ctx.search)); let found = !ctx.search.is_empty() && (tr(&format!("blk.{}", list[k].kind_name())).to_lowercase().contains(&ctx.search) || block_text(&list[k]).to_lowercase().contains(&ctx.search));
// slots: upper half before this block, lower half after it // slots: upper half before this block, lower half after it
slots.push((Rect::new(x, y - CH() * 0.3, w, CH() * ROW * 0.5 + CH() * 0.3), lp.to_vec(), k)); slots.push((Rect::new(x, y - CH() * 0.3, w, header.h * 0.5 + CH() * 0.3), lp.to_vec(), k));
slots.push((Rect::new(x, y + CH() * ROW * 0.5, w, CH() * ROW * 0.5), lp.to_vec(), k + 1)); slots.push((Rect::new(x, y + header.h * 0.5, w, header.h * 0.5), lp.to_vec(), k + 1));
let nested = matches!(list[k], Block::If { .. } | Block::Repeat { .. }); let nested = matches!(list[k], Block::If { .. } | Block::Repeat { .. });
// the block: header row with its fields // the block: header row with its fields
draw_block_shape(header, col, selected, found); draw_block_shape(header, col, selected, found);
@@ -433,7 +440,7 @@ impl BlockEdit {
im.tip(header, &tr(p)); im.tip(header, &tr(p));
} }
// grip: drag the block from here // grip: drag the block from here
let grip = Rect::new(x, y, CW() * 1.4, CH() * ROW); let grip = Rect::new(x, y, CW() * 1.4, header.h);
gfx.text("≡", x + CW() * 0.3, y + CH() * 0.6, S(), Color::new(1.0, 1.0, 1.0, 0.6)); gfx.text("≡", x + CW() * 0.3, y + CH() * 0.6, S(), Color::new(1.0, 1.0, 1.0, 0.6));
if im.pressed && im.over(grip) { if im.pressed && im.over(grip) {
im.pressed = false; im.pressed = false;
@@ -441,7 +448,7 @@ impl BlockEdit {
} }
let fy = y + (CH() * ROW - CH() * 1.5) * 0.5; let fy = y + (CH() * ROW - CH() * 1.5) * 0.5;
let mut cx = x + CW() * 1.8; let mut cx = x + CW() * 1.8;
self.block_fields(im, gfx, &mut list[k], &path, &mut cx, fy, ctx); self.block_fields(im, gfx, &mut list[k], &path, &mut cx, fy, x + CW() * 3.0, ctx);
// a click on the block's free area selects it // a click on the block's free area selects it
if im.clicked(header) { if im.clicked(header) {
ops.push(Op::Select(path.clone())); ops.push(Op::Select(path.clone()));
@@ -455,7 +462,7 @@ impl BlockEdit {
} }
self.help = tr(&format!("blk.help.{}", list[k].kind_name())); self.help = tr(&format!("blk.help.{}", list[k].kind_name()));
} }
y += CH() * ROW; y += header.h;
if nested { if nested {
// the C shape: bodies indented, with an "else" bar for if // the C shape: bodies indented, with an "else" bar for if
let inner_x = x + CW() * 2.5; let inner_x = x + CW() * 2.5;
@@ -496,7 +503,7 @@ impl BlockEdit {
// the fields inside a block's header row // the fields inside a block's header row
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn block_fields(&mut self, im: &mut Im, gfx: &Gfx, b: &mut Block, path: &[usize], cx: &mut f32, y: f32, ctx: &Ctx) { fn block_fields(&mut self, im: &mut Im, gfx: &Gfx, b: &mut Block, path: &[usize], cx: &mut f32, y: f32, x2: f32, ctx: &Ctx) {
let word = |cx: &mut f32, s: &str| { let word = |cx: &mut f32, s: &str| {
gfx.text(s, *cx, y + CH() * 0.25, S(), TXT); gfx.text(s, *cx, y + CH() * 0.25, S(), TXT);
*cx += gfx.measure(s, S()) + CW() * 0.6; *cx += gfx.measure(s, S()) + CW() * 0.6;
@@ -517,6 +524,13 @@ impl BlockEdit {
word(cx, &tr("blk.w_level")); word(cx, &tr("blk.w_level"));
self.value(im, gfx, level, Ty::Num, path, 3, cx, y, ctx); self.value(im, gfx, level, Ty::Num, path, 3, cx, y, ctx);
self.side_dd(im, gfx, side, false, path, 4, cx, y); self.side_dd(im, gfx, side, false, path, 4, cx, y);
// second row: where, and the name
let y = y + CH() * ROW;
*cx = x2;
let word = |cx: &mut f32, s: &str| {
gfx.text(s, *cx, y + CH() * 0.25, S(), TXT);
*cx += gfx.measure(s, S()) + CW() * 0.6;
};
word(cx, &tr("blk.w_at")); word(cx, &tr("blk.w_at"));
let mut opts = vec![tr("blk.place_core"), tr("blk.place_event")]; let mut opts = vec![tr("blk.place_core"), tr("blk.place_event")];
opts.extend(ctx.zones.iter().map(|z| trf("blk.place_zone", &[z.clone()]))); opts.extend(ctx.zones.iter().map(|z| trf("blk.place_zone", &[z.clone()])));
+12 -7
View File
@@ -91,6 +91,9 @@ impl MapEdit {
game.map.activate(); game.map.activate();
game.recount(); game.recount();
game.ents.retain(|e| e.alive); game.ents.retain(|e| e.alive);
// no fog of war while painting (it is worked out again when the map is stored)
game.map.explored.clear();
game.map.visible.clear();
let (cx, cy) = game.map.core; let (cx, cy) = game.map.core;
let mut cam = Cam::new(cx as f32 + 1.5, cy as f32 + 1.5); let mut cam = Cam::new(cx as f32 + 1.5, cy as f32 + 1.5);
cam.zoom = 28.0; cam.zoom = 28.0;
@@ -653,16 +656,16 @@ impl MapEdit {
} }
y += CH() * 2.3; y += CH() * 2.3;
// categories as a grid of buttons // categories as a grid of buttons
let cw = (bar_w - CW() * 2.5) / 3.0; let cw = (bar_w - CW() * 2.25) / 2.0;
for (k, c) in CATS.iter().enumerate() { for (k, c) in CATS.iter().enumerate() {
let r = Rect::new(x + (k % 3) as f32 * (cw + CW() * 0.25), y + (k / 3) as f32 * CH() * 1.9, cw, CH() * 1.7); let r = Rect::new(x + (k % 2) as f32 * (cw + CW() * 0.25), y + (k / 2) as f32 * CH() * 1.8, cw, CH() * 1.6);
let name = tr(&format!("ed.cat.{:?}", c).to_lowercase()); let name = tr(&format!("ed.cat.{:?}", c).to_lowercase());
if im.select(gfx, r, &name, self.cat == *c, TXT) { if im.select(gfx, r, &name, self.cat == *c, TXT) {
self.cat = *c; self.cat = *c;
self.item = 0; self.item = 0;
} }
} }
y += CH() * 6.0; y += CH() * 1.8 * ((CATS.len() + 1) / 2) as f32 + CH() * 0.4;
// the items of the category // the items of the category
let items: Vec<String> = match self.cat { let items: Vec<String> = match self.cat {
Cat::Terrain => TERRAIN.iter().map(|t| tr(&format!("ed.terrain.{}", t))).collect(), Cat::Terrain => TERRAIN.iter().map(|t| tr(&format!("ed.terrain.{}", t))).collect(),
@@ -780,14 +783,16 @@ impl MapEdit {
self.mirror = c as u8; self.mirror = c as u8;
} }
bx += CW() * 19.0; bx += CW() * 19.0;
if im.button(gfx, Rect::new(bx, by, CW() * 8.0, CH() * 1.7), &tr("ed.undo")) { let uw = gfx.measure(&tr("ed.undo"), S()) + CW() * 2.0;
if im.button(gfx, Rect::new(bx, by, uw, CH() * 1.7), &tr("ed.undo")) {
self.undo(); self.undo();
} }
bx += CW() * 8.5; bx += uw + CW() * 0.5;
if im.button(gfx, Rect::new(bx, by, CW() * 8.0, CH() * 1.7), &tr("ed.redo")) { let rw = gfx.measure(&tr("ed.redo"), S()) + CW() * 2.0;
if im.button(gfx, Rect::new(bx, by, rw, CH() * 1.7), &tr("ed.redo")) {
self.redo(); self.redo();
} }
bx += CW() * 9.0; bx += rw + CW();
let info = trf("ed.map_info", &[self.game.map.w.to_string(), self.game.map.h.to_string(), self.game.ents.iter().filter(|e| e.alive).count().to_string(), self.game.towers.len().to_string()]); let info = trf("ed.map_info", &[self.game.map.w.to_string(), self.game.map.h.to_string(), self.game.ents.iter().filter(|e| e.alive).count().to_string(), self.game.towers.len().to_string()]);
gfx.text(&info, bx, by + CH() * 0.3, SMALL(), DIM); gfx.text(&info, bx, by + CH() * 0.3, SMALL(), DIM);
gfx.text(&tr("ed.map_hint"), bar_w + CW(), sh() - CH() * 1.5, SMALL(), DIM); gfx.text(&tr("ed.map_hint"), bar_w + CW(), sh() - CH() * 1.5, SMALL(), DIM);
+42 -25
View File
@@ -207,6 +207,13 @@ impl Editor {
pub fn frame(&mut self, gfx: &Gfx, r3d: &mut R3d, dt: f32, now: f64) -> EdAction { pub fn frame(&mut self, gfx: &Gfx, r3d: &mut R3d, dt: f32, now: f64) -> EdAction {
clear_background(Color::from_rgba(4, 3, 5, 255)); clear_background(Color::from_rgba(4, 3, 5, 255));
self.im.begin(); self.im.begin();
// an open dialog takes the clicks: the page behind it only draws
let held = (self.im.pressed, self.im.released, self.im.wheel);
if self.open_dialog {
self.im.pressed = false;
self.im.released = false;
self.im.wheel = 0.0;
}
let mut act = EdAction::None; let mut act = EdAction::None;
match self.screen { match self.screen {
Screen::Map(i) => { Screen::Map(i) => {
@@ -236,6 +243,7 @@ impl Editor {
} }
} }
if self.open_dialog { if self.open_dialog {
(self.im.pressed, self.im.released, self.im.wheel) = held;
begin_ui(); begin_ui();
if let Some(a) = self.open_screen(gfx) { if let Some(a) = self.open_screen(gfx) {
act = a; act = a;
@@ -263,30 +271,18 @@ impl Editor {
let mut act = EdAction::None; let mut act = EdAction::None;
let (w, h) = (sw(), sh()); let (w, h) = (sw(), sh());
let im = &mut self.im; let im = &mut self.im;
// top bar: name, author, language, file and server buttons // top bar: the title and the buttons, then name, author, language
let bar_h = CH() * 5.0; let bar_h = CH() * 5.0;
panel(gfx, Rect::new(0.0, 0.0, w, bar_h), ""); panel(gfx, Rect::new(0.0, 0.0, w, bar_h), "");
gfx.text(&tr("ed.campaign"), CW(), CH() * 0.4, S(), FRAME_HI); gfx.text(&tr("ed.campaign"), CW(), CH() * 0.8, S(), FRAME_HI);
let fw = (CW() * 24.0).min(w * 0.25);
label(gfx, CW(), CH() * 2.0, &tr("ed.name"), DIM);
im.text_field(gfx, id("cname", 0), Rect::new(CW() * 8.0, CH() * 1.7, fw, CH() * 1.5), &mut self.doc.name, 40);
label(gfx, CW(), CH() * 3.5, &tr("ed.author"), DIM);
im.text_field(gfx, id("cauthor", 0), Rect::new(CW() * 8.0, CH() * 3.3, fw, CH() * 1.5), &mut self.doc.author, 32);
let lx = CW() * 9.0 + fw;
label(gfx, lx, CH() * 2.0, &tr("ed.language"), DIM);
let langs = languages();
let names: Vec<String> = langs.iter().map(|(_, n)| n.clone()).collect();
let cur = langs.iter().find(|(c, _)| *c == self.doc.language).map(|(_, n)| n.clone()).unwrap_or_else(|| self.doc.language.clone());
if let Some(k) = im.dropdown(gfx, id("clang", 0), Rect::new(lx, CH() * 3.3, CW() * 14.0, CH() * 1.5), &cur, &names) {
self.doc.language = langs[k].0.clone();
}
// buttons on the right
let labels = [tr("ed.play"), tr("ed.new"), tr("ed.open"), tr("ed.save_file"), tr("ed.save_server"), tr("ed.close")]; let labels = [tr("ed.play"), tr("ed.new"), tr("ed.open"), tr("ed.save_file"), tr("ed.save_server"), tr("ed.close")];
let bw = labels.iter().map(|l| gfx.measure(l, S())).fold(0.0, f32::max) + CW() * 2.0; let mut bx = w - CW();
let mut bx = w - CW() - bw;
for k in (0..labels.len()).rev() { for k in (0..labels.len()).rev() {
let r = Rect::new(bx, CH() * 0.6, bw, CH() * 1.7); let bw = gfx.measure(&labels[k], S()) + CW() * 2.0;
if im.button(gfx, r, &labels[k]) { bx -= bw;
let r = Rect::new(bx, CH() * 0.5, bw, CH() * 1.7);
let clicked = if k == 0 { im.button_c(gfx, r, &labels[k], GREEN) } else { im.button(gfx, r, &labels[k]) };
if clicked {
match k { match k {
0 => act = EdAction::Play, 0 => act = EdAction::Play,
1 => { 1 => {
@@ -303,18 +299,39 @@ impl Editor {
_ => act = EdAction::Close, _ => act = EdAction::Close,
} }
} }
bx -= bw + CW() * 0.5; bx -= CW() * 0.5;
} }
let y2 = CH() * 2.9;
let fw = (CW() * 22.0).min(w * 0.2);
let mut x = CW();
label(gfx, x, y2 + CH() * 0.25, &tr("ed.name"), DIM);
x += gfx.measure(&tr("ed.name"), S()) + CW();
im.text_field(gfx, id("cname", 0), Rect::new(x, y2, fw, CH() * 1.5), &mut self.doc.name, 40);
x += fw + CW() * 1.5;
label(gfx, x, y2 + CH() * 0.25, &tr("ed.author"), DIM);
x += gfx.measure(&tr("ed.author"), S()) + CW();
im.text_field(gfx, id("cauthor", 0), Rect::new(x, y2, fw * 0.8, CH() * 1.5), &mut self.doc.author, 32);
x += fw * 0.8 + CW() * 1.5;
label(gfx, x, y2 + CH() * 0.25, &tr("ed.language"), DIM);
x += gfx.measure(&tr("ed.language"), S()) + CW();
let langs = languages();
let names: Vec<String> = langs.iter().map(|(_, n)| n.clone()).collect();
let cur = langs.iter().find(|(c, _)| *c == self.doc.language).map(|(_, n)| n.clone()).unwrap_or_else(|| self.doc.language.clone());
if let Some(k) = im.dropdown(gfx, id("clang", 0), Rect::new(x, y2, CW() * 14.0, CH() * 1.5), &cur, &names) {
self.doc.language = langs[k].0.clone();
}
x += CW() * 15.5;
let mut publ = self.published; let mut publ = self.published;
if self.server_id.is_some() && im.toggle(gfx, w - CW() * 2.0 - bw * 3.5, CH() * 3.1, &tr("ed.published"), &mut publ) { if self.server_id.is_some() && im.toggle(gfx, x, y2, &tr("ed.published"), &mut publ) {
self.published = publ; self.published = publ;
act = EdAction::Publish(publ); act = EdAction::Publish(publ);
} }
let add = Rect::new(w - CW() - bw, CH() * 3.0, bw, CH() * 1.7); let al = tr("ed.add_mission");
if im.button(gfx, add, &tr("ed.add_mission")) { let aw = gfx.measure(&al, S()) + CW() * 2.0;
if im.button(gfx, Rect::new(w - CW() - aw, y2 - CH() * 0.1, aw, CH() * 1.7), &al) {
let n = self.doc.missions.len(); let n = self.doc.missions.len();
self.doc.add_mission(); self.doc.add_mission();
self.doc.missions[n].pos = (-self.graph.pan.x / CW() * 0.0 + 40.0 + (n % 5) as f32 * 220.0, 40.0 + (n / 5) as f32 * 140.0); self.doc.missions[n].pos = (40.0 + (n % 5) as f32 * 220.0, 40.0 + (n / 5) as f32 * 140.0);
} }
// the canvas: cards and arrows // the canvas: cards and arrows