diff --git a/odin/src/gui/panel_editor.odin b/odin/src/gui/panel_editor.odin index a19e93c..9371f67 100644 --- a/odin/src/gui/panel_editor.odin +++ b/odin/src/gui/panel_editor.odin @@ -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) diff --git a/odin/src/gui/pen_input.odin b/odin/src/gui/pen_input.odin new file mode 100644 index 0000000..b7d2ab1 --- /dev/null +++ b/odin/src/gui/pen_input.odin @@ -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 +} diff --git a/odin/src/gui/runtime.odin b/odin/src/gui/runtime.odin index cbe9004..b5b239a 100644 --- a/odin/src/gui/runtime.odin +++ b/odin/src/gui/runtime.odin @@ -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) }