From fce4a88c925a564c175c382ef1f0620a4a05b3ad Mon Sep 17 00:00:00 2001 From: aiongg Date: Mon, 26 Oct 2026 02:44:56 +0000 Subject: [PATCH] Timeline: comp tabs draw through craft-ui's extra-tabs hook Pin craft-libs crates to 3723e32a, which adds the group extra-tabs hook. The comp tabs follow the panel tabs directly, with no drag bar between them, and the group header holds only the lock and the panel menu. --- Cargo.lock | 12 +++--- Cargo.toml | 12 +++--- crates/pdf/README.md | 2 +- crates/ui-egui/src/dock.rs | 6 +-- crates/ui-egui/src/dock_ui.rs | 80 +++++++++++++++-------------------- xtask/src/pdf_deps.rs | 2 +- 6 files changed, 50 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6911da6a5..644e0fa05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1038,12 +1038,12 @@ dependencies = [ [[package]] name = "craft-docpath" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=3723e32ab104bfb3b73fb207dd06a212f8a30e4b#3723e32ab104bfb3b73fb207dd06a212f8a30e4b" [[package]] name = "craft-pdf" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=3723e32ab104bfb3b73fb207dd06a212f8a30e4b#3723e32ab104bfb3b73fb207dd06a212f8a30e4b" dependencies = [ "hayro-syntax", "thiserror 2.0.21", @@ -1052,7 +1052,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=3723e32ab104bfb3b73fb207dd06a212f8a30e4b#3723e32ab104bfb3b73fb207dd06a212f8a30e4b" dependencies = [ "egui", "log", @@ -1065,7 +1065,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=3723e32ab104bfb3b73fb207dd06a212f8a30e4b#3723e32ab104bfb3b73fb207dd06a212f8a30e4b" dependencies = [ "chrono", "dunce", @@ -2870,7 +2870,7 @@ checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a" [[package]] name = "hayro-jbig2" version = "0.3.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=3723e32ab104bfb3b73fb207dd06a212f8a30e4b#3723e32ab104bfb3b73fb207dd06a212f8a30e4b" dependencies = [ "hayro-ccitt", ] @@ -2884,7 +2884,7 @@ checksum = "c75ab947623ef4ccaa7acf0579edf7cbb5a73838e3839a7be73335e522f433a1" [[package]] name = "hayro-syntax" version = "0.7.2" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=3723e32ab104bfb3b73fb207dd06a212f8a30e4b#3723e32ab104bfb3b73fb207dd06a212f8a30e4b" dependencies = [ "hayro-jbig2", "hayro-jpeg2000", diff --git a/Cargo.toml b/Cargo.toml index 0e1fd2cd5..b0907c1fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ rust-version = "1.95" repository = "https://github.com/storytold/effectcraft" [workspace.dependencies] -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", version = "=0.1.0", default-features = false, features = ["read"] } -craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" } -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", features = ["egui"] } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b", version = "=0.1.0", default-features = false, features = ["read"] } +craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b" } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b", features = ["egui"] } # internal effectcraft-time = { path = "crates/time" } effectcraft-geom = { path = "crates/geom" } @@ -108,7 +108,7 @@ pollster = "1" # ui # Shared frontend components; keep this dependency above the engine boundary. # Shared UI and the complete egui family select reviewed immutable commits below. -craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", features = ["serde"] } +craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b", features = ["serde"] } # `default_fonts` and `monochrome_emoji_fonts` are explicit: the app's `FontDefinitions::default()` # and its `✔`/`▣` chrome glyphs need both, and kittest's own dev-dependencies do not propagate. egui = { version = "0.37", default-features = false, features = ["default_fonts", "monochrome_emoji_fonts"] } @@ -220,8 +220,8 @@ lto = "thin" codegen-units = 1 [patch.crates-io] -hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", version = "=0.7.2" } -hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", version = "=0.3.0" } +hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b", version = "=0.7.2" } +hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b", version = "=0.3.0" } ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } diff --git a/crates/pdf/README.md b/crates/pdf/README.md index 9801c0574..1be4b0fe5 100644 --- a/crates/pdf/README.md +++ b/crates/pdf/README.md @@ -138,7 +138,7 @@ ffmpeg elsewhere) to check the code tables against an independent implementation ## Shared parser setup and bounds The workspace pins `craft-pdf` to `craft-libs` Git revision -`1e6f127844ec61b3b8afdcc2bb76e7cfe470153f`, with `default-features = false` and only the explicit `read` +`3723e32ab104bfb3b73fb207dd06a212f8a30e4b`, with `default-features = false` and only the explicit `read` feature. This excludes the backend's bundled fonts and Adobe CMaps and preserves EffectCraft's font and asset policies. The shared parser does not change the supported drawing operators, image codecs, continuously rasterized SVG tree, editable shapes, or EPS interpreter. diff --git a/crates/ui-egui/src/dock.rs b/crates/ui-egui/src/dock.rs index 6cbd3475d..054aa3598 100644 --- a/crates/ui-egui/src/dock.rs +++ b/crates/ui-egui/src/dock.rs @@ -528,9 +528,9 @@ pub enum DocAction { } /// Room around a tab's label, as craft-ui gives its dock tabs. -pub const TAB_PADDING: f32 = 24.0; +const TAB_PADDING: f32 = 24.0; /// Width of the » button that lists the comp tabs with no room in the strip. -pub const DOC_OVERFLOW: f32 = 22.0; +const DOC_OVERFLOW: f32 = 22.0; /// Room for the × after the shown comp's name. const DOC_CLOSE: f32 = 16.0; @@ -543,7 +543,7 @@ fn doc_job(doc: &DocTab, font: &egui::FontId, color: egui::Color32) -> egui::tex } /// The width of one line of `job` at the fonts' current size. -pub fn text_width(ctx: &egui::Context, job: egui::text::LayoutJob) -> f32 { +fn text_width(ctx: &egui::Context, job: egui::text::LayoutJob) -> f32 { ctx.fonts_mut(|f| f.layout_job(job)).size().x } diff --git a/crates/ui-egui/src/dock_ui.rs b/crates/ui-egui/src/dock_ui.rs index 8089211f9..41977e667 100644 --- a/crates/ui-egui/src/dock_ui.rs +++ b/crates/ui-egui/src/dock_ui.rs @@ -632,7 +632,7 @@ struct EffectContent<'a> { titles: Vec<(PanelKind, String)>, swatches: Vec<(PanelKind, egui::Color32)>, docs: Vec, - /// Measures tab labels for the group header's width. + /// Measures the comp tabs for their width in the tab row. ctx: egui::Context, style: DockStyle, doc_actions: Vec, @@ -749,37 +749,14 @@ fn window_bodies(window: &Floating) -> usize { } impl EffectContent<'_> { - /// The header holds the shown panel's lock and ≡ at the strip's end and, in the Timeline's - /// group, the comp tabs before them. craft-ui puts the header after the group's drag bar, so - /// the comp tabs follow the panel tabs only when the header takes all the room the panel tabs - /// and the bar leave. The panel tabs keep their full width; comps without room go to ». - fn header_width(&self, panels: &[PanelKind], active: PanelKind, strip_width: f32) -> f32 { - let tools = if has_lock(active) { LOCK_W } else { 0.0 } + MENU_W; - if self.docs.is_empty() || !panels.contains(&PanelKind::Timeline) { - return tools.min(strip_width * 2.0 / 3.0).max(MENU_W); - } - let tabs: f32 = panels.iter().map(|p| self.tab_width(p)).sum(); - // Half a point keeps rounding from shrinking the panel tabs. - let room = strip_width - tabs - self.style.drag_bar_width - 0.5; - room.max(tools + dock::DOC_OVERFLOW).min(strip_width).max(0.0) + fn header_width(&self, active: PanelKind, strip_width: f32) -> f32 { + let lock = if has_lock(active) { LOCK_W } else { 0.0 }; + (lock + MENU_W).min(strip_width * 2.0 / 3.0).max(MENU_W) } - /// A panel tab's width as craft-ui lays it out: its label and the tab padding. - fn tab_width(&self, panel: &PanelKind) -> f32 { - let logical = self.logical_label(panel); - let (font, color) = (&self.style.font, self.style.text); - dock::text_width(&self.ctx, self.tab_job(panel, &logical, font, color)) + dock::TAB_PADDING - } - - /// A panel tab's text: what the panel shows, after the label swatch of its comp or layer. - fn tab_job(&self, panel: &PanelKind, logical: &str, font: &egui::FontId, color: egui::Color32) -> egui::text::LayoutJob { - let title = self.titles.iter().find(|(p, _)| p == panel).map_or(logical, |(_, s)| s.as_str()); - let mut job = egui::text::LayoutJob::default(); - if let Some((_, swatch)) = self.swatches.iter().find(|(p, _)| p == panel) { - job.append("■ ", 0.0, egui::TextFormat::simple(font.clone(), *swatch)); - } - job.append(title, 0.0, egui::TextFormat::simple(font.clone(), color)); - job + /// Whether the group of `panels` shows comp tabs: it holds the Timeline and comps are open. + fn has_doc_tabs(&self, panels: &[PanelKind]) -> bool { + panels.contains(&PanelKind::Timeline) && !self.docs.is_empty() } } @@ -816,36 +793,30 @@ impl DockContent for EffectContent<'_> { /// comp or layer. With comps open, the Timeline's own tab is just "Timeline"; each comp has a /// tab of its own after the group's panel tabs. fn label(&mut self, _ui: &egui::Ui, panel: &PanelKind, logical: &str, font: &egui::FontId, color: egui::Color32) -> egui::WidgetText { - if self.swatches.iter().any(|(p, _)| p == panel) { - return self.tab_job(panel, logical, font, color).into(); - } - self.titles.iter().find(|(p, _)| p == panel).map_or(logical, |(_, s)| s.as_str()).into() + let title = self.titles.iter().find(|(p, _)| p == panel).map_or(logical, |(_, s)| s.as_str()); + let Some((_, swatch)) = self.swatches.iter().find(|(p, _)| p == panel) else { return title.into() }; + let mut job = egui::text::LayoutJob::default(); + job.append("■ ", 0.0, egui::TextFormat::simple(font.clone(), *swatch)); + job.append(title, 0.0, egui::TextFormat::simple(font.clone(), color)); + job.into() } - fn group_header_width(&self, panels: &[PanelKind], active: &PanelKind, _floating: bool, strip_width: f32) -> f32 { - self.header_width(panels, *active, strip_width) + fn group_header_width(&self, _panels: &[PanelKind], active: &PanelKind, _floating: bool, strip_width: f32) -> f32 { + self.header_width(*active, strip_width) } - /// The Timeline's comp tabs (in its group, whichever panel is shown), then the viewer lock - /// and the ≡ panel menu of the shown panel. - fn group_header(&mut self, ui: &mut egui::Ui, panels: &[PanelKind], active: &PanelKind, _floating: bool) { + /// The viewer lock and the ≡ panel menu of the shown panel. + fn group_header(&mut self, ui: &mut egui::Ui, _panels: &[PanelKind], active: &PanelKind, _floating: bool) { let t = self.tokens; let header = ui.max_rect(); let menu = Rect::from_min_max(pos2((header.max.x - MENU_W).max(header.min.x), header.min.y), header.max); - let mut end = menu.min.x; if has_lock(*active) { - let lock = Rect::from_center_size(pos2(end - LOCK_W / 2.0, header.center().y), vec2(14.0, 14.0)); - end -= LOCK_W; + let lock = Rect::from_center_size(pos2(menu.min.x - LOCK_W / 2.0, header.center().y), vec2(14.0, 14.0)); let locked = self.app.ui.locked_tabs.contains(&active.id()); if dock::lock_button(ui, lock, *active, locked, &t, &mut self.app.auto) { self.locks.push(*active); } } - if panels.contains(&PanelKind::Timeline) && !self.docs.is_empty() { - let strip = Rect::from_min_max(header.min, pos2(end.max(header.min.x), header.max.y)); - let selected = *active == PanelKind::Timeline; - self.doc_actions.extend(dock::doc_tabs(ui, strip, &self.docs, selected, &self.style, &mut self.app.auto)); - } let rect = Rect::from_center_size(menu.center(), vec2(18.0, 18.0)); let response = ui.interact(rect, egui::Id::unique(("panel-menu-button", active.id())), Sense::click()); response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, response.enabled(), "panel menu")); @@ -858,4 +829,19 @@ impl DockContent for EffectContent<'_> { self.menu = Some((*active, rect.left_bottom())); } } + + fn group_extra_tabs_width(&self, panels: &[PanelKind], active: &PanelKind, _floating: bool) -> f32 { + if !self.has_doc_tabs(panels) { + return 0.0; + } + dock::doc_widths(&self.ctx, &self.docs, *active == PanelKind::Timeline, &self.style.font).iter().sum() + } + + /// The Timeline's comp tabs, after its group's panel tabs whichever panel is shown. + fn group_extra_tabs(&mut self, ui: &mut egui::Ui, panels: &[PanelKind], active: &PanelKind, _floating: bool) { + if self.has_doc_tabs(panels) { + let selected = *active == PanelKind::Timeline; + self.doc_actions.extend(dock::doc_tabs(ui, ui.max_rect(), &self.docs, selected, &self.style, &mut self.app.auto)); + } + } } diff --git a/xtask/src/pdf_deps.rs b/xtask/src/pdf_deps.rs index 6ba8bb759..75de44a4a 100644 --- a/xtask/src/pdf_deps.rs +++ b/xtask/src/pdf_deps.rs @@ -5,7 +5,7 @@ use std::process::Command; use serde_json::Value; -const REV: &str = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f"; +const REV: &str = "3723e32ab104bfb3b73fb207dd06a212f8a30e4b"; const PACKAGES: &[(&str, &str)] = &[("craft-pdf", "0.1.0"), ("hayro-syntax", "0.7.2"), ("hayro-jbig2", "0.3.0")]; pub fn run(root: &Path) -> Result<(), String> {