From 872ff03dd55819e8bbf1d728600ef86e5ad0459d Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 30 Jun 2026 16:32:30 -0400 Subject: [PATCH] editor-gui: a duration palette over set_selection_duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A toolbar palette — 1 / 1/2 / 1/4 / 1/8 / 1/16 — sets the selected note or rest's written value via set_selection_duration(NoteValue::whole_note_fraction), with the editor's make-room overwrite when lengthening; refusals (tuplet member, decomposed event, non-note selection) surface in the status line. The palette lives in the toolbar, which runs before the frame's rerender slot, so it has no stale-texture concern. Adds epiphany-core as a direct dependency for NoteValue (already a transitive workspace member, so no MSRV/CI impact). The module doc and crate description are updated; only an undo UI remains unbuilt. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NAtEiJtt9yKVV1zjKYmZhS --- Cargo.lock | 1 + crates/epiphany-editor-gui/Cargo.toml | 4 +++- crates/epiphany-editor-gui/src/main.rs | 30 +++++++++++++++++++++----- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5706f6..a449985 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1098,6 +1098,7 @@ name = "epiphany-editor-gui" version = "0.0.0" dependencies = [ "eframe", + "epiphany-core", "epiphany-editor-core", "epiphany-engrave", "epiphany-layout-ir", diff --git a/crates/epiphany-editor-gui/Cargo.toml b/crates/epiphany-editor-gui/Cargo.toml index d4400f0..ceb7c2a 100644 --- a/crates/epiphany-editor-gui/Cargo.toml +++ b/crates/epiphany-editor-gui/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" edition.workspace = true authors.workspace = true repository.workspace = true -description = "A thin native (egui) GUI shell over epiphany-editor-core: renders the score with epiphany-render-svg (rasterized by resvg), click to select, and a toolbar/keys for the note-editing intents. A vertical slice proving the editor seam — no duration editing, empty-space insert, or undo UI yet." +description = "A thin native (egui) GUI shell over epiphany-editor-core: renders the score with epiphany-render-svg (rasterized by resvg), click to select, a pencil mode that clicks notes onto the staff, and a toolbar/keys for the note-editing intents and a duration palette. A vertical slice proving the editor seam — no undo UI yet." # A demo binary, not a library other crates build on, so it carries the heavy GUI # dependency tree (eframe/winit/wgpu, resvg/usvg/tiny-skia) the rest of the @@ -14,6 +14,8 @@ description = "A thin native (egui) GUI shell over epiphany-editor-core: renders [dependencies] # The headless editor this GUI drives. epiphany-editor-core.workspace = true +# Musical-duration / note-value types, for the duration palette. +epiphany-core.workspace = true # The real constraint solver (so the GUI shows a genuinely engraved score). epiphany-engrave.workspace = true # SVG rendering of the resolved layout, and the layout/hit-test geometry types. diff --git a/crates/epiphany-editor-gui/src/main.rs b/crates/epiphany-editor-gui/src/main.rs index 3145434..271627c 100644 --- a/crates/epiphany-editor-gui/src/main.rs +++ b/crates/epiphany-editor-gui/src/main.rs @@ -7,11 +7,13 @@ //! `resvg` into an `egui` texture, resolves clicks back to world coordinates to //! select, and drives the note-editing intents from a toolbar and keyboard. //! -//! Intentionally narrow (per the GUI vertical-slice plan): no duration editing and no -//! undo UI — the debug panel just shows the selection and the last applied op, enough -//! to confirm the op log is usable. A **pencil mode** turns a click into a -//! click-to-insert ([`EditorSession::insert_note_at`]) with make-room overwrite, on a -//! quarter-note grid. The GUI is the thing meant to surface the next real core gaps. +//! Intentionally narrow (per the GUI vertical-slice plan): no undo UI — the debug +//! panel just shows the selection and the last applied op, enough to confirm the op log +//! is usable. A **pencil mode** turns a click into a click-to-insert +//! ([`EditorSession::insert_note_at`]) with make-room overwrite on a quarter-note grid, +//! and a **duration palette** resizes the selection +//! ([`EditorSession::set_selection_duration`]). The GUI is the thing meant to surface +//! the next real core gaps. //! //! It is a demo binary; there is no headless way to assert its rendering here, so the //! one piece of nontrivial logic — the screen↔world coordinate map a click depends on @@ -19,6 +21,7 @@ use eframe::egui; +use epiphany_core::NoteValue; use epiphany_editor_core::{EditOutcome, EditorError, EditorSession, GridResolution}; use epiphany_engrave::Engraver; use epiphany_layout_ir::{BoundingBox, HitShape, Point}; @@ -247,6 +250,23 @@ impl EditorApp { self.run("insert after", |s| s.insert_note_after_selection()); } ui.separator(); + // Duration palette: set the selected note/rest's written value (make-room + // overwrite when lengthening). + ui.label("Dur:"); + for (label, value) in [ + ("1", NoteValue::Whole), + ("1/2", NoteValue::Half), + ("1/4", NoteValue::Quarter), + ("1/8", NoteValue::Eighth), + ("1/16", NoteValue::Sixteenth), + ] { + if ui.button(label).clicked() { + self.run(&format!("duration {label}"), |s| { + s.set_selection_duration(value.whole_note_fraction()) + }); + } + } + ui.separator(); ui.toggle_value(&mut self.pencil, "✏ Pencil (insert)"); ui.separator(); if ui