Pixel-art mouse pointers and hand actions

- pointing monster hand with the active tool's icon, grabbing hand / fist, animated backhand and punch
- active tool highlighted in the toolbar (pulsing frame, arrow)
- hand: pick up unconscious heroes on own ground, drop prisoners anywhere on own ground
  (onto a free torture place = locked up), Shift+click backhand (speed-up) / punch (stun)
- fix: hint slots overflowed with the new hints; hint times are no longer saved
- test for the hand rules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 08:53:21 +01:00
1 parent 50d013bb28
commit f113e1085c
9 files changed
+372 -32

No files matched your search

+78 -2
View File
@@ -229,6 +229,7 @@ pub struct Ui {
tool_rects: Vec<Rect>,
end_button: Rect,
lang_rects: Vec<(Rect, String)>,
pub hand_anim: Option<(u8, f64)>, // 1 = backhand, 2 = punch, start time
}
fn wrap(s: &str, cols: usize) -> Vec<String> {
@@ -274,6 +275,7 @@ impl Ui {
tool_rects: Vec::new(),
end_button: Rect::new(0.0, 0.0, 0.0, 0.0),
lang_rects: Vec::new(),
hand_anim: None,
}
}
@@ -413,8 +415,14 @@ impl Ui {
hover_tool = Some(i);
}
let sel = self.tool == t;
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.35, 0.22, 0.1, 1.0) } else if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, if sel { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.42, 0.26, 0.1, 1.0) } else if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
// the active tool: pulsing frame and an arrow in the toolbar frame above it
let pulse = (now * 2.5) as i64 % 2 == 0;
let frame = if sel { if pulse { WHITE } else { FRAME_HI } } else { Color::new(0.35, 0.28, 0.2, 1.0) };
draw_rectangle_lines(r.x, r.y, r.w, r.h, if sel { 3.0 * px } else { 2.0 * px }, frame);
if sel {
gfx.glyph(0x1F, bx + CW() * 2.5, tb_y, S(), if pulse { WHITE } else { FRAME_HI });
}
let ti = tool_info(t);
// icon: the 16 px atlas cell at 2*F, centred on the grid
let icon = 32.0 * px;
@@ -581,6 +589,7 @@ impl Ui {
("", TXT),
("help.k_left", TXT),
("help.k_right", TXT),
("help.k_slap", TXT),
("help.k_pan", TXT),
("help.k_rot", TXT),
("help.k_zoom", TXT),
@@ -678,6 +687,73 @@ impl Ui {
}
}
impl Ui {
// Pixel-art mouse pointer: pointing hand with the active tool's icon, or the grabbing
// hand for the hand tool (fist while holding units, animated slap/punch)
pub fn draw_cursor(&self, gfx: &Gfx, g: &Game, now: f64, in_menu: bool) {
let (mx, my) = mouse();
let px = K();
let sc = 2.0 * px; // cursor pixel size
let anim = self.hand_anim.filter(|(_, t)| now - t < 0.25);
let hand_tool = self.tool == Tool::Hand && !in_menu;
let (frame, mut ox, mut oy) = match (anim, hand_tool) {
(Some((1, t)), _) => {
// backhand: swings from right to left
let k = ((now - t) / 0.25) as f32;
(CURSOR_BACKHAND, ((1.0 - 2.0 * k) * 6.0).round() * sc, 0.0)
}
(Some((_, t)), _) => {
// punch: short thrust forward and back
let k = ((now - t) / 0.25) as f32;
let d = if k < 0.4 { -k / 0.4 } else { -(1.0 - k) / 0.6 };
(CURSOR_PUNCH, (d * 3.0).round() * sc, (d * 3.0).round() * sc)
}
(None, true) if !g.hand.is_empty() => (CURSOR_FIST, 0.0, 0.0),
(None, true) => (CURSOR_OPEN, 0.0, 0.0),
_ => (CURSOR_POINT, 0.0, 0.0),
};
// idle: the pointing hand wiggles by one pixel
if frame == CURSOR_POINT && (now * 2.0) as i64 % 2 == 0 {
oy += sc;
}
if frame == CURSOR_OPEN && (now * 1.5) as i64 % 2 == 0 {
ox += sc;
}
let (hx, hy) = CURSOR_HOT[frame as usize];
let x = (mx - hx * sc + ox).floor();
let y = (my - hy * sc + oy).floor();
// units in the hand dangle below the fist
if hand_tool && !g.hand.is_empty() {
if let Some(e) = g.hand.last().and_then(|id| g.ents.iter().find(|e| e.id == *id)) {
let s = 10.0 * sc;
draw_texture_ex(
&gfx.sprites,
x + 3.0 * sc,
y + 12.0 * sc,
WHITE,
DrawTextureParams { dest_size: Some(vec2(s, s)), source: Some(Gfx::sprite_rect(e.kind, false)), rotation: 0.3, ..Default::default() },
);
}
gfx.text_sh(&g.hand.len().to_string(), x + 15.0 * sc, y + 10.0 * sc, S(), FRAME_HI);
}
draw_texture_ex(
&gfx.cursors,
x,
y,
WHITE,
DrawTextureParams { dest_size: Some(vec2(16.0 * sc, 16.0 * sc)), source: Some(Rect::new(frame as f32 * 16.0, 0.0, 16.0, 16.0)), ..Default::default() },
);
// small icon of the active action next to the pointing hand
if frame == CURSOR_POINT && !in_menu {
let icon = 16.0 * px;
let (ix, iy) = (x + 11.0 * sc, y + 11.0 * sc);
draw_rectangle(ix - px, iy - px, icon + 2.0 * px, icon + 2.0 * px, Color::new(0.05, 0.03, 0.04, 0.85));
draw_rectangle_lines(ix - px, iy - px, icon + 2.0 * px, icon + 2.0 * px, px, FRAME);
gfx.draw_cell(tool_info(self.tool).icon, ix, iy, icon, icon, WHITE);
}
}
}
// Info text for the unit or tile under the mouse (first line = title)
pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec<(String, Color)> {
let mut out = Vec::new();