Phase 4: Pen tablet pressure support via Pen_Tablet_State
- gui/pen_input.odin: Pen_Tablet_State struct with pressure/tilt/eraser tracking - pen_tablet_init/cleanup lifecycle - pen_tablet_poll: tracks pressure ramp on mouse down (simulated pressure) - pen_tablet_pressure_for_stroke helper - Designed for XInput2 extension later (API ready, Linux X11 FFI deferred) - runtime.odin: GUI_App_State.pen field, init/cleanup in main loop, poll before editor update - panel_editor.odin: Pressure-aware brush thickness - effective_size = brush_size * pen.pressure when stylus active - Eraser auto-detection via pen.eraser flag - Pressure indicator in toolbar (P: XX%) - ERASER label shown when pen reports eraser mode Build passes. 155/156 tests pass (1 pre-existing CLI/TUI failure).
This commit is contained in:
parent
4445574b43
commit
898322a398
@ -414,6 +414,8 @@ editor_update :: proc(app: ^GUI_App_State) {
|
||||
ed := &app.editor
|
||||
if !ed.active { return }
|
||||
|
||||
pen := &app.pen
|
||||
|
||||
mouse := rl.GetMousePosition()
|
||||
cr := ed.canvas_rect
|
||||
mx := vec2_x(mouse)
|
||||
@ -472,11 +474,15 @@ editor_update :: proc(app: ^GUI_App_State) {
|
||||
clear(&ed.current_stroke.points)
|
||||
append(&ed.current_stroke.points, canvas_pos)
|
||||
ed.current_stroke.color = ed.brush_color
|
||||
ed.current_stroke.thickness = ed.brush_size
|
||||
effective_size := ed.brush_size
|
||||
if pen.available || pen.stylus_down {
|
||||
effective_size = ed.brush_size * pen.pressure
|
||||
}
|
||||
ed.current_stroke.thickness = effective_size
|
||||
ed.current_stroke.tool = ed.current_tool
|
||||
if ed.current_tool == .Eraser {
|
||||
if ed.current_tool == .Eraser || pen.eraser {
|
||||
ed.current_stroke.color = rl.Color{0, 0, 0, 0}
|
||||
ed.current_stroke.thickness = ed.brush_size * 2
|
||||
ed.current_stroke.thickness = effective_size * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -744,7 +750,16 @@ editor_render_toolbar :: proc(app: ^GUI_App_State) {
|
||||
zoom_text := fmt.tprintf("Zoom: %.0f%%", ed.zoom * 100)
|
||||
rl.DrawText(to_cstr(zoom_text), c.int(zoom_x), c.int(start_y + 6), 11, rl.Color{160, 160, 180, 255})
|
||||
|
||||
adjust_x := zoom_x + 90
|
||||
if app.pen.stylus_down || app.pen.available {
|
||||
pen_x := zoom_x + 90
|
||||
pen_text := fmt.tprintf("P: %d%%", c.int(app.pen.pressure * 100))
|
||||
rl.DrawText(to_cstr(pen_text), c.int(pen_x), c.int(start_y + 6), 11, rl.Color{130, 200, 130, 255})
|
||||
if app.pen.eraser {
|
||||
rl.DrawText("ERASER", c.int(pen_x + 55), c.int(start_y + 6), 11, rl.Color{255, 130, 130, 255})
|
||||
}
|
||||
}
|
||||
|
||||
adjust_x := zoom_x + 180
|
||||
adjust_bg := rl.Color{28, 28, 42, 255}
|
||||
if ed.show_adjust_panel { adjust_bg = rl.Color{99, 102, 241, 255} }
|
||||
rl.DrawRectangle(c.int(adjust_x), c.int(start_y), c.int(60), c.int(btn_h), adjust_bg)
|
||||
|
||||
63
odin/src/gui/pen_input.odin
Normal file
63
odin/src/gui/pen_input.odin
Normal file
@ -0,0 +1,63 @@
|
||||
package gui
|
||||
|
||||
import "core:c"
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
Pen_Tablet_State :: struct {
|
||||
available: bool,
|
||||
pressure: f32,
|
||||
tilt_x: f32,
|
||||
tilt_y: f32,
|
||||
eraser: bool,
|
||||
device_name: string,
|
||||
stylus_down: bool,
|
||||
prev_pressure: f32,
|
||||
}
|
||||
|
||||
PEN_PRESSURE_MIN :: 0.05
|
||||
PEN_PRESSURE_MAX :: 1.0
|
||||
|
||||
pen_tablet_init :: proc(state: ^Pen_Tablet_State) {
|
||||
state.available = false
|
||||
state.pressure = 1.0
|
||||
state.tilt_x = 0
|
||||
state.tilt_y = 0
|
||||
state.eraser = false
|
||||
state.device_name = ""
|
||||
state.stylus_down = false
|
||||
state.prev_pressure = 1.0
|
||||
}
|
||||
|
||||
pen_tablet_poll :: proc(state: ^Pen_Tablet_State) {
|
||||
state.prev_pressure = state.pressure
|
||||
|
||||
if rl.IsMouseButtonDown(.LEFT) {
|
||||
if !state.stylus_down {
|
||||
state.stylus_down = true
|
||||
state.pressure = PEN_PRESSURE_MIN
|
||||
}
|
||||
state.pressure = min(state.pressure + 0.05, PEN_PRESSURE_MAX)
|
||||
} else {
|
||||
state.stylus_down = false
|
||||
state.pressure = 1.0
|
||||
}
|
||||
|
||||
state.eraser = rl.IsMouseButtonDown(.MIDDLE)
|
||||
state.tilt_x = 0
|
||||
state.tilt_y = 0
|
||||
}
|
||||
|
||||
pen_tablet_cleanup :: proc(state: ^Pen_Tablet_State) {
|
||||
if len(state.device_name) > 0 {
|
||||
delete(state.device_name)
|
||||
state.device_name = ""
|
||||
}
|
||||
state.available = false
|
||||
}
|
||||
|
||||
pen_tablet_pressure_for_stroke :: proc(state: ^Pen_Tablet_State) -> f32 {
|
||||
if !state.stylus_down { return 1.0 }
|
||||
return state.pressure
|
||||
}
|
||||
@ -55,6 +55,8 @@ GUI_App_State :: struct {
|
||||
field_buf: [FIELD_BUF_SIZE]u8,
|
||||
// ─── Panel Editor state ──────────────────────────────────────
|
||||
editor: Panel_Editor_State,
|
||||
// ─── Pen tablet state ────────────────────────────────────────
|
||||
pen: Pen_Tablet_State,
|
||||
// ─── Animation state ──────────────────────────────────────────
|
||||
sidebar_anim: f32, // current animated sidebar width (px)
|
||||
overlay_alpha: f32, // 0→1 fade progress for active overlay
|
||||
@ -228,6 +230,8 @@ run_gui_app :: proc(state: ^core.Comic_State) -> shared.App_Error {
|
||||
defer delete(app.status_msg)
|
||||
defer unload_panel_textures(&app.panel_textures)
|
||||
defer editor_close(&app)
|
||||
pen_tablet_init(&app.pen)
|
||||
defer pen_tablet_cleanup(&app.pen)
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
screen_w := rl.GetScreenWidth()
|
||||
@ -494,6 +498,7 @@ run_gui_app :: proc(state: ^core.Comic_State) -> shared.App_Error {
|
||||
|
||||
// ─── Panel Editor overlay (raylib, after Clay) ────────────────
|
||||
if app.editor.active {
|
||||
pen_tablet_poll(&app.pen)
|
||||
editor_update(&app)
|
||||
editor_render(&app)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user