diff --git a/src/backend/wayland/state/toolbar/events/tests.rs b/src/backend/wayland/state/toolbar/events/tests.rs index eced9190..71bf79f9 100644 --- a/src/backend/wayland/state/toolbar/events/tests.rs +++ b/src/backend/wayland/state/toolbar/events/tests.rs @@ -8,6 +8,7 @@ use crate::config::ToolbarLayoutMode; use crate::draw::{Color, FontDescriptor}; use crate::input::state::test_support::make_test_input_state; use crate::input::{EraserMode, Tool}; +use crate::ui::toolbar::ToolbarSideSection; use anyhow::anyhow; use std::fs; use std::path::{Path, PathBuf}; @@ -77,6 +78,7 @@ fn runtime_toolbar_events_do_not_directly_save_config() { ToolbarEvent::SaveSessionAsCancel, ToolbarEvent::SessionInfo, ToolbarEvent::ClearSession, + ToolbarEvent::ToggleSideSectionCollapsed(ToolbarSideSection::Session, true), ]; for event in events { diff --git a/src/backend/wayland/toolbar/layout/side/actions.rs b/src/backend/wayland/toolbar/layout/side/actions.rs index 025625ac..c74fdc7d 100644 --- a/src/backend/wayland/toolbar/layout/side/actions.rs +++ b/src/backend/wayland/toolbar/layout/side/actions.rs @@ -1,5 +1,6 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarLayoutSpec, format_binding_label}; use crate::backend::wayland::toolbar::rows::{centered_grid_layout, grid_layout, row_item_width}; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui::toolbar::model::{ ToolbarActionsModel, ToolbarCommandGroup, ToolbarCommandGroupKind, }; @@ -14,6 +15,14 @@ pub(super) fn push_actions_hits( }; let actions_card_h = ctx.spec.side_actions_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Actions, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Actions) + { + return y + actions_card_h + ctx.section_gap; + } + let mut action_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let mut has_group = false; for group in model.groups() { diff --git a/src/backend/wayland/toolbar/layout/side/arrow.rs b/src/backend/wayland/toolbar/layout/side/arrow.rs index cc97025b..7eb275f8 100644 --- a/src/backend/wayland/toolbar/layout/side/arrow.rs +++ b/src/backend/wayland/toolbar/layout/side/arrow.rs @@ -1,16 +1,95 @@ -use super::{SideLayoutContext, ToolbarLayoutSpec}; -use crate::ui::toolbar::ToolContext; +use super::{HitKind, HitRegion, SideLayoutContext, ToolbarEvent, ToolbarLayoutSpec}; +use crate::ui::toolbar::{ToolContext, ToolbarSideSection}; -// Arrow hit regions are built in render to avoid duplicate registrations. -pub(super) fn advance_arrow_section(ctx: &SideLayoutContext<'_>, y: f64) -> f64 { +pub(super) fn push_arrow_section_hits( + ctx: &SideLayoutContext<'_>, + y: f64, + hits: &mut Vec, +) -> f64 { if !ToolContext::from_snapshot(ctx.snapshot).show_arrow_labels { return y; } - let card_h = if ctx.snapshot.arrow_label_enabled { - ToolbarLayoutSpec::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET - } else { - ToolbarLayoutSpec::SIDE_TOGGLE_CARD_HEIGHT - }; + let card_h = ctx.spec.side_arrow_labels_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit( + ctx, + y, + ToolbarSideSection::ArrowLabels, + hits, + ); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::ArrowLabels) + { + return y + card_h + ctx.section_gap; + } + + let toggle_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + hits.push(HitRegion { + rect: ( + ctx.x, + toggle_y, + ctx.content_width, + ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT, + ), + event: ToolbarEvent::ToggleArrowLabels(!ctx.snapshot.arrow_label_enabled), + kind: HitKind::Click, + tooltip: Some("Auto-number arrows 1, 2, 3.".to_string()), + }); + if ctx.snapshot.arrow_label_enabled { + let reset_y = + toggle_y + ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT + ToolbarLayoutSpec::SIDE_TOGGLE_GAP; + hits.push(HitRegion { + rect: ( + ctx.x, + reset_y, + ctx.content_width, + ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT, + ), + event: ToolbarEvent::ResetArrowLabelCounter, + kind: HitKind::Click, + tooltip: Some("Reset numbering to 1.".to_string()), + }); + } + + y + card_h + ctx.section_gap +} + +pub(super) fn push_step_marker_hits( + ctx: &SideLayoutContext<'_>, + y: f64, + hits: &mut Vec, +) -> f64 { + if !ToolContext::from_snapshot(ctx.snapshot).show_step_counter { + return y; + } + + let card_h = ctx.spec.side_step_markers_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit( + ctx, + y, + ToolbarSideSection::StepMarkers, + hits, + ); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::StepMarkers) + { + return y + card_h + ctx.section_gap; + } + + let reset_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + hits.push(HitRegion { + rect: ( + ctx.x, + reset_y, + ctx.content_width, + ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT, + ), + event: ToolbarEvent::ResetStepMarkerCounter, + kind: HitKind::Click, + tooltip: Some("Reset numbering to 1.".to_string()), + }); + y + card_h + ctx.section_gap } diff --git a/src/backend/wayland/toolbar/layout/side/boards.rs b/src/backend/wayland/toolbar/layout/side/boards.rs index 13ad89e5..28751265 100644 --- a/src/backend/wayland/toolbar/layout/side/boards.rs +++ b/src/backend/wayland/toolbar/layout/side/boards.rs @@ -1,5 +1,6 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarLayoutSpec, format_binding_label}; use crate::backend::wayland::toolbar::rows::{capped_grid_columns, grid_layout, row_item_width}; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui::toolbar::model::toolbar_boards_model; pub(super) fn push_boards_hits( @@ -12,6 +13,14 @@ pub(super) fn push_boards_hits( }; let boards_card_h = ctx.spec.side_boards_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Boards, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Boards) + { + return y + boards_card_h + ctx.section_gap; + } + let boards_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let btn_h = if ctx.use_icons { ToolbarLayoutSpec::SIDE_ACTION_BUTTON_HEIGHT_ICON diff --git a/src/backend/wayland/toolbar/layout/side/colors.rs b/src/backend/wayland/toolbar/layout/side/colors.rs index 3f5edcca..50f9140b 100644 --- a/src/backend/wayland/toolbar/layout/side/colors.rs +++ b/src/backend/wayland/toolbar/layout/side/colors.rs @@ -1,10 +1,20 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarEvent, ToolbarLayoutSpec}; +use crate::ui::toolbar::ToolbarSideSection; pub(super) fn push_color_picker_hits( ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec, ) -> f64 { + let card_h = ctx.spec.side_colors_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Colors, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Colors) + { + return y + card_h + ctx.section_gap; + } + let picker_y = y + ToolbarLayoutSpec::SIDE_COLOR_PICKER_OFFSET_Y; let picker_h = ctx.spec.side_color_picker_height(ctx.snapshot); hits.push(HitRegion { @@ -19,5 +29,5 @@ pub(super) fn push_color_picker_hits( tooltip: None, }); - y + ctx.spec.side_colors_height(ctx.snapshot) + ctx.section_gap + y + card_h + ctx.section_gap } diff --git a/src/backend/wayland/toolbar/layout/side/delay.rs b/src/backend/wayland/toolbar/layout/side/delay.rs index 995ddf34..151a5288 100644 --- a/src/backend/wayland/toolbar/layout/side/delay.rs +++ b/src/backend/wayland/toolbar/layout/side/delay.rs @@ -2,65 +2,92 @@ use super::{ HitKind, HitRegion, SideLayoutContext, ToolbarEvent, ToolbarLayoutSpec, delay_secs_from_t, delay_t_from_ms, }; +use crate::ui::toolbar::ToolbarSideSection; pub(super) fn push_delay_hits( ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec, ) -> f64 { - if ctx.snapshot.show_step_section - && ctx.snapshot.show_delay_sliders - && ctx.snapshot.drawer_open - && ctx.snapshot.drawer_tab == crate::input::ToolbarDrawerTab::App + if !ctx.snapshot.show_step_section + || !ctx.snapshot.drawer_open + || ctx.snapshot.drawer_tab != crate::input::ToolbarDrawerTab::App { - let undo_t = delay_t_from_ms(ctx.snapshot.undo_all_delay_ms); - let redo_t = delay_t_from_ms(ctx.snapshot.redo_all_delay_ms); - let toggles_h = - ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT * 2.0 + ToolbarLayoutSpec::SIDE_TOGGLE_GAP; - let custom_h = if ctx.snapshot.custom_section_enabled { - ToolbarLayoutSpec::SIDE_CUSTOM_SECTION_HEIGHT - } else { - 0.0 - }; - let slider_start_y = y - + ToolbarLayoutSpec::SIDE_STEP_HEADER_HEIGHT - + toggles_h - + custom_h - + ToolbarLayoutSpec::SIDE_STEP_SLIDER_TOP_PADDING; - let slider_hit_h = ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HEIGHT - + ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HIT_PADDING * 2.0; - let undo_y = slider_start_y + ToolbarLayoutSpec::SIDE_DELAY_SLIDER_UNDO_OFFSET_Y; - hits.push(HitRegion { - rect: ( - ctx.x, - undo_y - ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HIT_PADDING, - ctx.content_width, - slider_hit_h, - ), - event: ToolbarEvent::SetUndoDelay(delay_secs_from_t(undo_t)), - kind: HitKind::DragUndoDelay, - tooltip: None, - }); - let redo_y = slider_start_y + ToolbarLayoutSpec::SIDE_DELAY_SLIDER_REDO_OFFSET_Y; - hits.push(HitRegion { - rect: ( - ctx.x, - redo_y - ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HIT_PADDING, - ctx.content_width, - slider_hit_h, - ), - event: ToolbarEvent::SetRedoDelay(delay_secs_from_t(redo_t)), - kind: HitKind::DragRedoDelay, - tooltip: None, - }); + return y; } - if ctx.snapshot.show_step_section - && ctx.snapshot.drawer_open - && ctx.snapshot.drawer_tab == crate::input::ToolbarDrawerTab::App + let card_h = ctx.spec.side_step_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::StepUndo, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::StepUndo) { - y + ctx.spec.side_step_height(ctx.snapshot) + ctx.section_gap - } else { - y + return y + card_h + ctx.section_gap; + } + + let toggle_h = ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT; + let toggle_gap = ToolbarLayoutSpec::SIDE_TOGGLE_GAP; + let custom_toggle_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + hits.push(HitRegion { + rect: (ctx.x, custom_toggle_y, ctx.content_width, toggle_h), + event: ToolbarEvent::ToggleCustomSection(!ctx.snapshot.custom_section_enabled), + kind: HitKind::Click, + tooltip: Some("Step controls: multi-step undo/redo.".to_string()), + }); + let delay_toggle_y = custom_toggle_y + toggle_h + toggle_gap; + hits.push(HitRegion { + rect: (ctx.x, delay_toggle_y, ctx.content_width, toggle_h), + event: ToolbarEvent::ToggleDelaySliders(!ctx.snapshot.show_delay_sliders), + kind: HitKind::Click, + tooltip: Some("Delay sliders: undo/redo delays.".to_string()), + }); + + if ctx.snapshot.show_delay_sliders { + push_delay_slider_hits(ctx, y, hits); } + + y + card_h + ctx.section_gap +} + +fn push_delay_slider_hits(ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec) { + let undo_t = delay_t_from_ms(ctx.snapshot.undo_all_delay_ms); + let redo_t = delay_t_from_ms(ctx.snapshot.redo_all_delay_ms); + let toggles_h = + ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT * 2.0 + ToolbarLayoutSpec::SIDE_TOGGLE_GAP; + let custom_h = if ctx.snapshot.custom_section_enabled { + ToolbarLayoutSpec::SIDE_CUSTOM_SECTION_HEIGHT + } else { + 0.0 + }; + let slider_start_y = y + + ToolbarLayoutSpec::SIDE_STEP_HEADER_HEIGHT + + toggles_h + + custom_h + + ToolbarLayoutSpec::SIDE_STEP_SLIDER_TOP_PADDING; + let slider_hit_h = ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HEIGHT + + ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HIT_PADDING * 2.0; + let undo_y = slider_start_y + ToolbarLayoutSpec::SIDE_DELAY_SLIDER_UNDO_OFFSET_Y; + hits.push(HitRegion { + rect: ( + ctx.x, + undo_y - ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HIT_PADDING, + ctx.content_width, + slider_hit_h, + ), + event: ToolbarEvent::SetUndoDelay(delay_secs_from_t(undo_t)), + kind: HitKind::DragUndoDelay, + tooltip: None, + }); + let redo_y = slider_start_y + ToolbarLayoutSpec::SIDE_DELAY_SLIDER_REDO_OFFSET_Y; + hits.push(HitRegion { + rect: ( + ctx.x, + redo_y - ToolbarLayoutSpec::SIDE_DELAY_SLIDER_HIT_PADDING, + ctx.content_width, + slider_hit_h, + ), + event: ToolbarEvent::SetRedoDelay(delay_secs_from_t(redo_t)), + kind: HitKind::DragRedoDelay, + tooltip: None, + }); } diff --git a/src/backend/wayland/toolbar/layout/side/mod.rs b/src/backend/wayland/toolbar/layout/side/mod.rs index afc1d09b..c4f9edbd 100644 --- a/src/backend/wayland/toolbar/layout/side/mod.rs +++ b/src/backend/wayland/toolbar/layout/side/mod.rs @@ -7,6 +7,7 @@ mod drawer; mod header; mod pages; mod presets; +mod section_header; mod session; mod settings; mod sliders; @@ -46,23 +47,33 @@ pub fn build_side_hits( y = sliders::push_thickness_hits(&ctx, y, hits); if tool_context.show_eraser_mode { - y += ToolbarLayoutSpec::SIDE_ERASER_MODE_CARD_HEIGHT + ctx.section_gap; + y = sliders::push_eraser_mode_hits(&ctx, y, hits); + } + + if tool_context.show_polygon_sides_control { + y = sliders::push_polygon_sides_hits(&ctx, y, hits); } } // Arrow section: only for arrow tool if tool_context.show_arrow_labels { - y = arrow::advance_arrow_section(&ctx, y); + y = arrow::push_arrow_section_hits(&ctx, y, hits); + } + + // Step marker counter: only for step marker tool + if tool_context.show_step_counter { + y = arrow::push_step_marker_hits(&ctx, y, hits); } // Marker opacity: only for marker tool if tool_context.show_marker_opacity { - y += ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT + ctx.section_gap; + y = sliders::push_marker_opacity_hits(&ctx, y, hits); } // Text controls: only when text/note is active if tool_context.show_font_controls { - y = sliders::push_text_hits(&ctx, y, hits); + y = sliders::push_text_size_hits(&ctx, y, hits); + y = sliders::push_font_hits(&ctx, y, hits); } y = drawer::push_drawer_tabs_hits(&ctx, y, hits); @@ -83,7 +94,6 @@ pub(super) struct SideLayoutContext<'a> { pub(super) content_width: f64, pub(super) use_icons: bool, pub(super) section_gap: f64, - pub(super) show_text_controls: bool, } impl<'a> SideLayoutContext<'a> { @@ -93,8 +103,6 @@ impl<'a> SideLayoutContext<'a> { let x = ToolbarLayoutSpec::SIDE_START_X; let content_width = spec.side_content_width(width); let section_gap = ToolbarLayoutSpec::SIDE_SECTION_GAP; - let show_text_controls = - snapshot.text_active || snapshot.note_active || snapshot.show_text_controls; Self { width, snapshot, @@ -103,7 +111,6 @@ impl<'a> SideLayoutContext<'a> { content_width, use_icons, section_gap, - show_text_controls, } } } diff --git a/src/backend/wayland/toolbar/layout/side/pages.rs b/src/backend/wayland/toolbar/layout/side/pages.rs index 3d2fc08a..7aea1e3e 100644 --- a/src/backend/wayland/toolbar/layout/side/pages.rs +++ b/src/backend/wayland/toolbar/layout/side/pages.rs @@ -1,5 +1,6 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarLayoutSpec, format_binding_label}; use crate::backend::wayland::toolbar::rows::{grid_layout, row_item_width}; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui::toolbar::model::toolbar_pages_model; pub(super) fn push_pages_hits( @@ -12,6 +13,14 @@ pub(super) fn push_pages_hits( }; let pages_card_h = ctx.spec.side_pages_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Pages, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Pages) + { + return y + pages_card_h + ctx.section_gap; + } + let pages_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let btn_h = if ctx.use_icons { ToolbarLayoutSpec::SIDE_ACTION_BUTTON_HEIGHT_ICON diff --git a/src/backend/wayland/toolbar/layout/side/presets.rs b/src/backend/wayland/toolbar/layout/side/presets.rs index ff2f45e7..354dbbf3 100644 --- a/src/backend/wayland/toolbar/layout/side/presets.rs +++ b/src/backend/wayland/toolbar/layout/side/presets.rs @@ -1,11 +1,11 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarEvent, ToolbarLayoutSpec}; +use crate::ui::toolbar::ToolbarSideSection; pub(super) fn push_preset_hits( ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec, ) -> f64 { - let presets_card_h = ToolbarLayoutSpec::SIDE_PRESET_CARD_HEIGHT; let slot_count = ctx .snapshot .preset_slot_count @@ -14,6 +14,15 @@ pub(super) fn push_preset_hits( return y; } + let presets_card_h = ctx.spec.side_presets_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Presets, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Presets) + { + return y + presets_card_h + ctx.section_gap; + } + let slot_size = ToolbarLayoutSpec::SIDE_PRESET_SLOT_SIZE; let slot_gap = ToolbarLayoutSpec::SIDE_PRESET_SLOT_GAP; let slot_row_y = y + ToolbarLayoutSpec::SIDE_PRESET_ROW_OFFSET_Y; diff --git a/src/backend/wayland/toolbar/layout/side/section_header.rs b/src/backend/wayland/toolbar/layout/side/section_header.rs new file mode 100644 index 00000000..e1c9324f --- /dev/null +++ b/src/backend/wayland/toolbar/layout/side/section_header.rs @@ -0,0 +1,26 @@ +use super::{HitKind, HitRegion, SideLayoutContext, ToolbarLayoutSpec}; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; + +pub(super) fn push_collapsible_header_hit( + ctx: &SideLayoutContext<'_>, + y: f64, + section: ToolbarSideSection, + hits: &mut Vec, +) { + let collapsed = ctx.snapshot.side_section_collapsed(section); + hits.push(HitRegion { + rect: ( + ctx.spec.side_card_x(), + y, + ctx.spec.side_card_width(ctx.width), + ToolbarLayoutSpec::SIDE_COLLAPSE_HEADER_HIT_HEIGHT, + ), + event: ToolbarEvent::ToggleSideSectionCollapsed(section, !collapsed), + kind: HitKind::Click, + tooltip: Some(format!( + "{} {}", + if collapsed { "Expand" } else { "Collapse" }, + section.label() + )), + }); +} diff --git a/src/backend/wayland/toolbar/layout/side/session.rs b/src/backend/wayland/toolbar/layout/side/session.rs index 558f6f9a..4070abcd 100644 --- a/src/backend/wayland/toolbar/layout/side/session.rs +++ b/src/backend/wayland/toolbar/layout/side/session.rs @@ -2,6 +2,7 @@ use super::{HitKind, HitRegion, SideLayoutContext}; use crate::backend::wayland::toolbar::format_binding_label; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::backend::wayland::toolbar::rows::{grid_layout, row_item_width}; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui::toolbar::model::ToolbarSessionModel; pub(super) fn push_session_hits( @@ -14,6 +15,14 @@ pub(super) fn push_session_hits( }; let card_h = ctx.spec.side_session_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Session, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Session) + { + return y + card_h + ctx.section_gap; + } + let mut row_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y + ToolbarLayoutSpec::SIDE_SESSION_META_HEIGHT diff --git a/src/backend/wayland/toolbar/layout/side/settings.rs b/src/backend/wayland/toolbar/layout/side/settings.rs index 7ede4cd1..da15c1d6 100644 --- a/src/backend/wayland/toolbar/layout/side/settings.rs +++ b/src/backend/wayland/toolbar/layout/side/settings.rs @@ -1,5 +1,6 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarLayoutSpec}; use crate::backend::wayland::toolbar::rows::{grid_layout, row_item_width}; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui::toolbar::model::{ToolbarActivation, ToolbarSettingsModel}; pub(super) fn push_settings_hits(ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec) { @@ -7,6 +8,14 @@ pub(super) fn push_settings_hits(ctx: &SideLayoutContext<'_>, y: f64, hits: &mut return; }; + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Settings, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Settings) + { + return; + } + let toggle_h = ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT; let toggle_gap = ToolbarLayoutSpec::SIDE_TOGGLE_GAP; let toggles = settings_model.toggles(); diff --git a/src/backend/wayland/toolbar/layout/side/sliders.rs b/src/backend/wayland/toolbar/layout/side/sliders.rs index 17da3ca9..dde5586f 100644 --- a/src/backend/wayland/toolbar/layout/side/sliders.rs +++ b/src/backend/wayland/toolbar/layout/side/sliders.rs @@ -1,11 +1,21 @@ use super::{HitKind, HitRegion, SideLayoutContext, ToolbarEvent, ToolbarLayoutSpec}; use crate::ui::toolbar::model::ToolbarSliderSpec; +use crate::ui::toolbar::{ToolContext, ToolbarSideSection}; pub(super) fn push_thickness_hits( ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec, ) -> f64 { + let card_h = ctx.spec.side_thickness_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Thickness, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Thickness) + { + return y + card_h + ctx.section_gap; + } + let slider_row_y = y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; let slider_hit_h = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; hits.push(HitRegion { @@ -40,16 +50,69 @@ pub(super) fn push_thickness_hits( tooltip: None, }); - let mut next_y = y + ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT + ctx.section_gap; - if crate::ui::toolbar::snapshot::ToolContext::from_snapshot(ctx.snapshot) - .show_polygon_sides_control + y + card_h + ctx.section_gap +} + +pub(super) fn push_eraser_mode_hits( + ctx: &SideLayoutContext<'_>, + y: f64, + hits: &mut Vec, +) -> f64 { + let card_h = ctx.spec.side_eraser_mode_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit( + ctx, + y, + ToolbarSideSection::EraserMode, + hits, + ); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::EraserMode) { - next_y = push_polygon_sides_hits(ctx, next_y, hits); + return y + card_h + ctx.section_gap; } - next_y + + let toggle_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + hits.push(HitRegion { + rect: ( + ctx.x, + toggle_y, + ctx.content_width, + ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT, + ), + event: ToolbarEvent::SetEraserMode( + if ctx.snapshot.eraser_mode == crate::input::EraserMode::Stroke { + crate::input::EraserMode::Brush + } else { + crate::input::EraserMode::Stroke + }, + ), + kind: HitKind::Click, + tooltip: Some("Erase by stroke".to_string()), + }); + + y + card_h + ctx.section_gap } -fn push_polygon_sides_hits(ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec) -> f64 { +pub(super) fn push_polygon_sides_hits( + ctx: &SideLayoutContext<'_>, + y: f64, + hits: &mut Vec, +) -> f64 { + let card_h = ctx.spec.side_polygon_sides_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit( + ctx, + y, + ToolbarSideSection::PolygonSides, + hits, + ); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::PolygonSides) + { + return y + card_h + ctx.section_gap; + } + let row_y = y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; let btn = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; hits.push(HitRegion { @@ -71,18 +134,27 @@ fn push_polygon_sides_hits(ctx: &SideLayoutContext<'_>, y: f64, hits: &mut Vec, y: f64, hits: &mut Vec, ) -> f64 { - if !ctx.show_text_controls { + if !ToolContext::from_snapshot(ctx.snapshot).show_font_controls { return y; } + let card_h = ctx.spec.side_text_size_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::TextSize, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::TextSize) + { + return y + card_h + ctx.section_gap; + } + let text_slider_row_y = y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; hits.push(HitRegion { rect: ( @@ -96,8 +168,97 @@ pub(super) fn push_text_hits( tooltip: None, }); - y + ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT - + ctx.section_gap - + ToolbarLayoutSpec::SIDE_FONT_CARD_HEIGHT - + ctx.section_gap + y + card_h + ctx.section_gap +} + +pub(super) fn push_marker_opacity_hits( + ctx: &SideLayoutContext<'_>, + y: f64, + hits: &mut Vec, +) -> f64 { + if !ToolContext::from_snapshot(ctx.snapshot).show_marker_opacity { + return y; + } + + let card_h = ctx.spec.side_marker_opacity_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit( + ctx, + y, + ToolbarSideSection::MarkerOpacity, + hits, + ); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::MarkerOpacity) + { + return y + card_h + ctx.section_gap; + } + + let slider_row_y = y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; + hits.push(HitRegion { + rect: ( + ctx.x, + slider_row_y, + ctx.content_width, + ToolbarLayoutSpec::SIDE_NUDGE_SIZE, + ), + event: ToolbarEvent::SetMarkerOpacity(ctx.snapshot.marker_opacity), + kind: HitKind::DragSetMarkerOpacity { + min: ToolbarSliderSpec::MARKER_OPACITY.min, + max: ToolbarSliderSpec::MARKER_OPACITY.max, + }, + tooltip: None, + }); + + y + card_h + ctx.section_gap +} + +pub(super) fn push_font_hits( + ctx: &SideLayoutContext<'_>, + y: f64, + hits: &mut Vec, +) -> f64 { + if !ToolContext::from_snapshot(ctx.snapshot).show_font_controls { + return y; + } + + let card_h = ctx.spec.side_font_height(ctx.snapshot); + super::section_header::push_collapsible_header_hit(ctx, y, ToolbarSideSection::Font, hits); + if ctx + .snapshot + .side_section_collapsed(ToolbarSideSection::Font) + { + return y + card_h + ctx.section_gap; + } + + let font_y = y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + let font_gap = ToolbarLayoutSpec::SIDE_FONT_BUTTON_GAP; + let font_w = (ctx.content_width - font_gap) / 2.0; + for idx in 0..2 { + hits.push(HitRegion { + rect: ( + ctx.x + idx as f64 * (font_w + font_gap), + font_y, + font_w, + ToolbarLayoutSpec::SIDE_FONT_BUTTON_HEIGHT, + ), + event: ToolbarEvent::SetFont(if idx == 0 { + crate::draw::FontDescriptor::new( + "Sans".to_string(), + "bold".to_string(), + "normal".to_string(), + ) + } else { + crate::draw::FontDescriptor::new( + "Monospace".to_string(), + "normal".to_string(), + "normal".to_string(), + ) + }), + kind: HitKind::Click, + tooltip: None, + }); + } + + y + card_h + ctx.section_gap } diff --git a/src/backend/wayland/toolbar/layout/spec/side/constants.rs b/src/backend/wayland/toolbar/layout/spec/side/constants.rs index ea023142..dfd54537 100644 --- a/src/backend/wayland/toolbar/layout/spec/side/constants.rs +++ b/src/backend/wayland/toolbar/layout/spec/side/constants.rs @@ -96,4 +96,8 @@ impl ToolbarLayoutSpec { pub(in crate::backend::wayland::toolbar) const SIDE_SESSION_BUTTON_HEIGHT: f64 = 24.0; pub(in crate::backend::wayland::toolbar) const SIDE_SESSION_RECENT_HEIGHT: f64 = 22.0; pub(in crate::backend::wayland::toolbar) const SIDE_SESSION_ROW_GAP: f64 = 5.0; + pub(in crate::backend::wayland::toolbar) const SIDE_COLLAPSED_SECTION_HEIGHT: f64 = 30.0; + pub(in crate::backend::wayland::toolbar) const SIDE_COLLAPSE_HEADER_HIT_HEIGHT: f64 = + Self::SIDE_SECTION_TOGGLE_OFFSET_Y - 1.0; + pub(in crate::backend::wayland::toolbar) const SIDE_COLLAPSE_CHEVRON_SIZE: f64 = 14.0; } diff --git a/src/backend/wayland/toolbar/layout/spec/side/sizes.rs b/src/backend/wayland/toolbar/layout/spec/side/sizes.rs index c2317582..15e61d30 100644 --- a/src/backend/wayland/toolbar/layout/spec/side/sizes.rs +++ b/src/backend/wayland/toolbar/layout/spec/side/sizes.rs @@ -1,10 +1,10 @@ use crate::backend::wayland::toolbar::rows::capped_grid_columns; -use crate::ui::toolbar::ToolbarSnapshot; use crate::ui::toolbar::model::{ ToolbarActionsModel, ToolbarCommandGroupKind, ToolbarSessionModel, ToolbarSettingsModel, toolbar_boards_model, toolbar_pages_model, }; use crate::ui::toolbar::snapshot::ToolContext; +use crate::ui::toolbar::{ToolbarSideSection, ToolbarSnapshot}; use super::super::ToolbarLayoutSpec; @@ -36,51 +36,40 @@ impl ToolbarLayoutSpec { } }; - // Color section: only when tool needs color if tool_context.needs_color { let colors_h = self.side_colors_height(snapshot); add_section(colors_h, &mut height); } if show_presets { - add_section(Self::SIDE_PRESET_CARD_HEIGHT, &mut height); + add_section(self.side_presets_height(snapshot), &mut height); } - // Thickness/size slider: only when tool needs it if tool_context.needs_thickness { - add_section(Self::SIDE_SLIDER_CARD_HEIGHT, &mut height); + add_section(self.side_thickness_height(snapshot), &mut height); if tool_context.show_eraser_mode { - add_section(Self::SIDE_ERASER_MODE_CARD_HEIGHT, &mut height); + add_section(self.side_eraser_mode_height(snapshot), &mut height); } if tool_context.show_polygon_sides_control { - add_section(Self::SIDE_SLIDER_CARD_HEIGHT, &mut height); + add_section(self.side_polygon_sides_height(snapshot), &mut height); } } - // Arrow controls: only when arrow tool is active if tool_context.show_arrow_labels { - let arrow_height = if snapshot.arrow_label_enabled { - Self::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET - } else { - Self::SIDE_TOGGLE_CARD_HEIGHT - }; - add_section(arrow_height, &mut height); + add_section(self.side_arrow_labels_height(snapshot), &mut height); } - // Step marker controls: only when step marker is active if tool_context.show_step_counter { - add_section(Self::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET, &mut height); + add_section(self.side_step_markers_height(snapshot), &mut height); } - // Marker opacity: only when marker tool is active if tool_context.show_marker_opacity { - add_section(Self::SIDE_SLIDER_CARD_HEIGHT, &mut height); + add_section(self.side_marker_opacity_height(snapshot), &mut height); } - // Text controls: only when text/note mode is active if tool_context.show_font_controls { - add_section(Self::SIDE_SLIDER_CARD_HEIGHT, &mut height); // Text size - add_section(Self::SIDE_FONT_CARD_HEIGHT, &mut height); + add_section(self.side_text_size_height(snapshot), &mut height); + add_section(self.side_font_height(snapshot), &mut height); } if snapshot.drawer_open { @@ -143,6 +132,9 @@ impl ToolbarLayoutSpec { &self, snapshot: &ToolbarSnapshot, ) -> f64 { + if snapshot.side_section_collapsed(ToolbarSideSection::Colors) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let rows = 1.0 + if snapshot.show_more_colors { 1.0 } else { 0.0 }; let preview_row_h = Self::SIDE_COLOR_PREVIEW_SIZE + Self::SIDE_COLOR_PREVIEW_GAP_TOP @@ -154,6 +146,106 @@ impl ToolbarLayoutSpec { + (Self::SIDE_COLOR_SWATCH + Self::SIDE_COLOR_SWATCH_GAP) * rows } + pub(in crate::backend::wayland::toolbar) fn side_presets_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::Presets, + Self::SIDE_PRESET_CARD_HEIGHT, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_thickness_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::Thickness, + Self::SIDE_SLIDER_CARD_HEIGHT, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_eraser_mode_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::EraserMode, + Self::SIDE_ERASER_MODE_CARD_HEIGHT, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_polygon_sides_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::PolygonSides, + Self::SIDE_SLIDER_CARD_HEIGHT, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_arrow_labels_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + let expanded = if snapshot.arrow_label_enabled { + Self::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET + } else { + Self::SIDE_TOGGLE_CARD_HEIGHT + }; + self.collapsible_section_height(snapshot, ToolbarSideSection::ArrowLabels, expanded) + } + + pub(in crate::backend::wayland::toolbar) fn side_step_markers_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::StepMarkers, + Self::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_marker_opacity_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::MarkerOpacity, + Self::SIDE_SLIDER_CARD_HEIGHT, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_text_size_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::TextSize, + Self::SIDE_SLIDER_CARD_HEIGHT, + ) + } + + pub(in crate::backend::wayland::toolbar) fn side_font_height( + &self, + snapshot: &ToolbarSnapshot, + ) -> f64 { + self.collapsible_section_height( + snapshot, + ToolbarSideSection::Font, + Self::SIDE_FONT_CARD_HEIGHT, + ) + } + pub(in crate::backend::wayland::toolbar) fn side_actions_content_height( &self, snapshot: &ToolbarSnapshot, @@ -219,6 +311,9 @@ impl ToolbarLayoutSpec { &self, snapshot: &ToolbarSnapshot, ) -> f64 { + if snapshot.side_section_collapsed(ToolbarSideSection::Actions) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let content = self.side_actions_content_height(snapshot); if content <= 0.0 { 0.0 @@ -244,6 +339,9 @@ impl ToolbarLayoutSpec { let Some(pages) = toolbar_pages_model(snapshot) else { return 0.0; }; + if snapshot.side_section_collapsed(ToolbarSideSection::Pages) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let btn_h = if self.use_icons { Self::SIDE_ACTION_BUTTON_HEIGHT_ICON } else { @@ -263,6 +361,9 @@ impl ToolbarLayoutSpec { let Some(boards) = toolbar_boards_model(snapshot) else { return 0.0; }; + if snapshot.side_section_collapsed(ToolbarSideSection::Boards) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let btn_h = if self.use_icons { Self::SIDE_ACTION_BUTTON_HEIGHT_ICON } else { @@ -279,6 +380,9 @@ impl ToolbarLayoutSpec { &self, snapshot: &ToolbarSnapshot, ) -> f64 { + if snapshot.side_section_collapsed(ToolbarSideSection::StepUndo) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let delay_h = if snapshot.show_delay_sliders { Self::SIDE_DELAY_SECTION_HEIGHT } else { @@ -299,6 +403,9 @@ impl ToolbarLayoutSpec { &self, snapshot: &ToolbarSnapshot, ) -> f64 { + if snapshot.side_section_collapsed(ToolbarSideSection::Settings) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let toggle_h = Self::SIDE_TOGGLE_HEIGHT; let toggle_gap = Self::SIDE_TOGGLE_GAP; let Some(settings) = ToolbarSettingsModel::from_snapshot(snapshot) else { @@ -323,6 +430,9 @@ impl ToolbarLayoutSpec { let Some(session) = ToolbarSessionModel::from_snapshot(snapshot) else { return 0.0; }; + if snapshot.side_section_collapsed(ToolbarSideSection::Session) { + return Self::SIDE_COLLAPSED_SECTION_HEIGHT; + } let button_rows = session.button_rows(); let buttons_h = if button_rows == 0 { 0.0 @@ -373,4 +483,17 @@ impl ToolbarLayoutSpec { pub(in crate::backend::wayland::toolbar) fn side_card_width(&self, width: f64) -> f64 { width - 2.0 * Self::SIDE_START_X + Self::SIDE_CARD_INSET * 2.0 } + + fn collapsible_section_height( + &self, + snapshot: &ToolbarSnapshot, + section: ToolbarSideSection, + expanded_height: f64, + ) -> f64 { + if snapshot.side_section_collapsed(section) { + Self::SIDE_COLLAPSED_SECTION_HEIGHT + } else { + expanded_height + } + } } diff --git a/src/backend/wayland/toolbar/layout/tests/collapsible.rs b/src/backend/wayland/toolbar/layout/tests/collapsible.rs new file mode 100644 index 00000000..3c8196fb --- /dev/null +++ b/src/backend/wayland/toolbar/layout/tests/collapsible.rs @@ -0,0 +1,317 @@ +use super::{ + ToolbarLayoutSpec, build_side_hits, create_test_input_state, side_size, snapshot_from_state, +}; +use crate::backend::wayland::toolbar::events::HitKind; +use crate::backend::wayland::toolbar::hit::HitRegion; +use crate::input::{Tool, ToolbarDrawerTab}; +use crate::ui::toolbar::{ + SessionRecentSnapshot, ToolbarEvent, ToolbarSideSection, ToolbarSnapshot, +}; + +fn rendered_side_hits(snapshot: &ToolbarSnapshot) -> Vec { + let (w, h) = side_size(snapshot); + let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32).unwrap(); + let ctx = cairo::Context::new(&surface).unwrap(); + let mut hits = Vec::new(); + crate::backend::wayland::toolbar::render_side_palette( + &ctx, w as f64, h as f64, snapshot, &mut hits, None, None, + ) + .unwrap(); + hits +} + +fn static_side_hits(snapshot: &ToolbarSnapshot) -> Vec { + let (w, h) = side_size(snapshot); + let mut hits = Vec::new(); + build_side_hits(w as f64, h as f64, snapshot, &mut hits); + hits +} + +fn assert_expand_hits(hits: &[HitRegion], sections: &[ToolbarSideSection]) { + for section in sections { + assert!( + hits.iter().any(|hit| matches!( + hit.event, + ToolbarEvent::ToggleSideSectionCollapsed(hit_section, false) + if hit_section == *section + )), + "missing expand hit for {section:?}" + ); + } +} + +#[test] +fn collapsed_header_hit_excludes_body_start_boundary() { + let mut state = create_test_input_state(); + state.toolbar_drawer_open = true; + state.toolbar_drawer_tab = ToolbarDrawerTab::App; + state.show_settings_section = true; + let snapshot = snapshot_from_state(&state); + let hits = static_side_hits(&snapshot); + let header = hits + .iter() + .find(|hit| { + matches!( + hit.event, + ToolbarEvent::ToggleSideSectionCollapsed(ToolbarSideSection::Settings, true) + ) + }) + .expect("settings collapse header"); + let body_start_y = header.rect.1 + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + + assert!( + !header.contains(header.rect.0 + header.rect.2 / 2.0, body_start_y), + "collapse header must not contain the first body row boundary" + ); +} + +#[test] +fn side_session_collapsed_hides_body_hits_and_keeps_expand_hit() { + let mut state = create_test_input_state(); + state.toolbar_drawer_open = true; + state.toolbar_drawer_tab = ToolbarDrawerTab::App; + + let mut expanded = snapshot_from_state(&state); + expanded.active_session_path = + Some(std::path::PathBuf::from("/tmp/current.wayscriber-session")); + expanded.active_session_name = Some("current.wayscriber-session".to_string()); + expanded.recent_sessions = vec![SessionRecentSnapshot { + display_name: "recent.wayscriber-session".to_string(), + path: std::path::PathBuf::from("/tmp/recent.wayscriber-session"), + }]; + + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::Session); + let mut collapsed = snapshot_from_state(&state); + collapsed.active_session_path = expanded.active_session_path.clone(); + collapsed.active_session_name = expanded.active_session_name.clone(); + collapsed.recent_sessions = expanded.recent_sessions.clone(); + + let expanded_size = side_size(&expanded); + let collapsed_size = side_size(&collapsed); + assert!( + collapsed_size.1 < expanded_size.1, + "collapsed Session section should reduce side toolbar height" + ); + + let (w, h) = collapsed_size; + let mut hits = Vec::new(); + build_side_hits(w as f64, h as f64, &collapsed, &mut hits); + + assert!(hits.iter().any(|hit| matches!( + hit.event, + ToolbarEvent::ToggleSideSectionCollapsed(ToolbarSideSection::Session, false) + ))); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::OpenSession)), + "collapsed Session section should hide Open hit" + ); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::OpenRecentSession(_))), + "collapsed Session section should hide recent hits" + ); +} + +#[test] +fn side_settings_collapsed_hides_body_hits_and_keeps_expand_hit() { + let mut state = create_test_input_state(); + state.toolbar_drawer_open = true; + state.toolbar_drawer_tab = ToolbarDrawerTab::App; + state.show_settings_section = true; + let expanded = snapshot_from_state(&state); + + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::Settings); + let collapsed = snapshot_from_state(&state); + + let expanded_size = side_size(&expanded); + let collapsed_size = side_size(&collapsed); + assert!( + collapsed_size.1 < expanded_size.1, + "collapsed Settings section should reduce side toolbar height" + ); + + let (w, h) = collapsed_size; + let mut hits = Vec::new(); + build_side_hits(w as f64, h as f64, &collapsed, &mut hits); + + assert!(hits.iter().any(|hit| matches!( + hit.event, + ToolbarEvent::ToggleSideSectionCollapsed(ToolbarSideSection::Settings, false) + ))); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::ToggleContextAwareUi(_))), + "collapsed Settings section should hide setting toggles" + ); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::OpenConfigFile)), + "collapsed Settings section should hide action buttons" + ); +} + +#[test] +fn common_side_sections_collapsed_keep_only_headers() { + let mut state = create_test_input_state(); + state.toolbar_drawer_open = true; + state.toolbar_drawer_tab = ToolbarDrawerTab::App; + state.show_step_section = true; + + let sections = [ + ToolbarSideSection::Colors, + ToolbarSideSection::Presets, + ToolbarSideSection::Thickness, + ToolbarSideSection::TextSize, + ToolbarSideSection::Font, + ToolbarSideSection::Actions, + ToolbarSideSection::StepUndo, + ToolbarSideSection::Session, + ToolbarSideSection::Settings, + ]; + state + .toolbar_collapsed_side_sections + .extend(sections.iter().copied()); + + let mut collapsed = snapshot_from_state(&state); + collapsed.active_session_path = + Some(std::path::PathBuf::from("/tmp/current.wayscriber-session")); + collapsed.active_session_name = Some("current.wayscriber-session".to_string()); + collapsed.recent_sessions = vec![SessionRecentSnapshot { + display_name: "recent.wayscriber-session".to_string(), + path: std::path::PathBuf::from("/tmp/recent.wayscriber-session"), + }]; + + state.toolbar_collapsed_side_sections.clear(); + let mut expanded = snapshot_from_state(&state); + expanded.active_session_path = collapsed.active_session_path.clone(); + expanded.active_session_name = collapsed.active_session_name.clone(); + expanded.recent_sessions = collapsed.recent_sessions.clone(); + assert!(side_size(&collapsed).1 < side_size(&expanded).1); + + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, §ions); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.kind, HitKind::PickColor { .. })) + ); + assert!(!hits.iter().any(|hit| matches!( + hit.event, + ToolbarEvent::SetThickness(_) + | ToolbarEvent::SetFontSize(_) + | ToolbarEvent::SetFont(_) + | ToolbarEvent::OpenSession + | ToolbarEvent::OpenRecentSession(_) + | ToolbarEvent::ToggleContextAwareUi(_) + | ToolbarEvent::ToggleCustomSection(_) + ))); + } +} + +#[test] +fn drawer_view_sections_collapsed_keep_only_headers() { + let mut state = create_test_input_state(); + state.toolbar_drawer_open = true; + state.toolbar_drawer_tab = ToolbarDrawerTab::View; + let sections = [ToolbarSideSection::Boards, ToolbarSideSection::Pages]; + state + .toolbar_collapsed_side_sections + .extend(sections.iter().copied()); + let collapsed = snapshot_from_state(&state); + + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, §ions); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::PageNew | ToolbarEvent::BoardNew)) + ); + } +} + +#[test] +fn tool_specific_side_sections_collapsed_keep_only_headers() { + let mut state = create_test_input_state(); + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::EraserMode); + state.set_tool_override(Some(Tool::Eraser)); + let collapsed = snapshot_from_state(&state); + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, &[ToolbarSideSection::EraserMode]); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::SetEraserMode(_))) + ); + } + + let mut state = create_test_input_state(); + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::PolygonSides); + state.set_tool_override(Some(Tool::RegularPolygon)); + let collapsed = snapshot_from_state(&state); + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, &[ToolbarSideSection::PolygonSides]); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::NudgePolygonSides(_))) + ); + } + + let mut state = create_test_input_state(); + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::ArrowLabels); + state.set_tool_override(Some(Tool::Arrow)); + let collapsed = snapshot_from_state(&state); + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, &[ToolbarSideSection::ArrowLabels]); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::ToggleArrowLabels(_))) + ); + } + + let mut state = create_test_input_state(); + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::MarkerOpacity); + state.set_tool_override(Some(Tool::Marker)); + let collapsed = snapshot_from_state(&state); + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, &[ToolbarSideSection::MarkerOpacity]); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::NudgeMarkerOpacity(_))) + ); + } + + let mut state = create_test_input_state(); + state + .toolbar_collapsed_side_sections + .insert(ToolbarSideSection::StepMarkers); + state.set_tool_override(Some(Tool::StepMarker)); + let collapsed = snapshot_from_state(&state); + for hits in [static_side_hits(&collapsed), rendered_side_hits(&collapsed)] { + assert_expand_hits(&hits, &[ToolbarSideSection::StepMarkers]); + assert!( + !hits + .iter() + .any(|hit| matches!(hit.event, ToolbarEvent::ResetStepMarkerCounter)) + ); + } +} diff --git a/src/backend/wayland/toolbar/layout/tests/mod.rs b/src/backend/wayland/toolbar/layout/tests/mod.rs index 1475ba7a..345a5824 100644 --- a/src/backend/wayland/toolbar/layout/tests/mod.rs +++ b/src/backend/wayland/toolbar/layout/tests/mod.rs @@ -10,6 +10,8 @@ use crate::ui::toolbar::{ SessionRecentSnapshot, ToolbarBindingHints, ToolbarEvent, ToolbarSnapshot, }; +mod collapsible; + fn create_test_input_state() -> InputState { let keybindings = KeybindingsConfig::default(); let action_map = keybindings.build_action_map().unwrap(); diff --git a/src/backend/wayland/toolbar/render/side_palette/actions.rs b/src/backend/wayland/toolbar/render/side_palette/actions.rs index 445e9207..bd36f53c 100644 --- a/src/backend/wayland/toolbar/render/side_palette/actions.rs +++ b/src/backend/wayland/toolbar/render/side_palette/actions.rs @@ -5,16 +5,16 @@ use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::backend::wayland::toolbar::rows::row_item_width; use crate::toolbar_icons; -use crate::ui::toolbar::ToolbarEvent; use crate::ui::toolbar::ToolbarSnapshot; use crate::ui::toolbar::model::{ ToolbarActionsModel, ToolbarButtonModel, ToolbarCommandGroup, ToolbarCommandGroupKind, }; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; use super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL}; use super::super::widgets::draw_group_card; -use super::super::widgets::draw_section_label; +use super::section_header::draw_collapsible_header; use helpers::{ ActionButton, ActionIconFn, IconActionLayout, TextActionLayout, render_icon_action_group, render_text_action_group, @@ -23,7 +23,6 @@ use helpers::{ pub(super) fn draw_actions_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -44,14 +43,20 @@ pub(super) fn draw_actions_section(layout: &mut SidePaletteLayout, y: &mut f64) let actions_card_h = layout.spec.side_actions_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, actions_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, - "Actions", + ToolbarSideSection::Actions, + ToolbarSideSection::Actions.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if snapshot.side_section_collapsed(ToolbarSideSection::Actions) { + *y += actions_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let actions_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; if use_icons { diff --git a/src/backend/wayland/toolbar/render/side_palette/arrow.rs b/src/backend/wayland/toolbar/render/side_palette/arrow.rs index ab9e3a45..e1f07dbc 100644 --- a/src/backend/wayland/toolbar/render/side_palette/arrow.rs +++ b/src/backend/wayland/toolbar/render/side_palette/arrow.rs @@ -6,13 +6,14 @@ use super::SidePaletteLayout; use crate::backend::wayland::toolbar::events::HitKind; use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; -use crate::ui::toolbar::{ToolContext, ToolbarEvent}; +use crate::ui::toolbar::{ToolContext, ToolbarEvent, ToolbarSideSection}; use crate::ui_text::{UiTextStyle, text_layout}; +use super::section_header::draw_collapsible_header; + pub(super) fn draw_arrow_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -36,29 +37,32 @@ pub(super) fn draw_arrow_section(layout: &mut SidePaletteLayout, y: &mut f64) { return; } - let card_h = if snapshot.arrow_label_enabled { - ToolbarLayoutSpec::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET - } else { - ToolbarLayoutSpec::SIDE_TOGGLE_CARD_HEIGHT - }; + let card_h = layout.spec.side_arrow_labels_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, + ToolbarSideSection::ArrowLabels, "Arrow labels", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); if snapshot.arrow_label_enabled { let hint = format!("Next: {}", snapshot.arrow_label_next); let layout = text_layout(ctx, hint_style, &hint, None); let ext = layout.ink_extents(); - let hint_x = card_x + card_w - ext.width() - 8.0 - ext.x_bearing(); + let chevron_reserve = ToolbarLayoutSpec::SIDE_COLLAPSE_CHEVRON_SIZE + 10.0; + let hint_x = card_x + card_w - ext.width() - chevron_reserve - ext.x_bearing(); let hint_y = *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL; set_color(ctx, COLOR_LABEL_HINT); layout.show_at_baseline(ctx, hint_x, hint_y); } + if snapshot.side_section_collapsed(ToolbarSideSection::ArrowLabels) { + *y += card_h + section_gap; + return; + } + let hits = &mut layout.hits; let toggle_h = ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT; let toggle_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let toggle_hover = hover diff --git a/src/backend/wayland/toolbar/render/side_palette/boards.rs b/src/backend/wayland/toolbar/render/side_palette/boards.rs index e83fa325..ab78dd17 100644 --- a/src/backend/wayland/toolbar/render/side_palette/boards.rs +++ b/src/backend/wayland/toolbar/render/side_palette/boards.rs @@ -5,17 +5,17 @@ use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::backend::wayland::toolbar::rows::{capped_grid_columns, grid_layout, row_item_width}; use crate::toolbar_icons; -use crate::ui::toolbar::ToolbarEvent; use crate::ui::toolbar::model::toolbar_boards_model; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; use super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL}; use super::super::widgets::*; +use super::section_header::draw_collapsible_header; pub(super) fn draw_boards_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -39,7 +39,14 @@ pub(super) fn draw_boards_section(layout: &mut SidePaletteLayout, y: &mut f64) { // Section label with keybinding hint let label_y = *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL; - draw_section_label(ctx, label_style, x, label_y, "Boards"); + draw_collapsible_header( + layout, + *y, + label_style, + ToolbarSideSection::Boards, + ToolbarSideSection::Boards.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, + ); // Draw keybinding hint for board picker (right-aligned) if let Some(binding) = snapshot @@ -54,11 +61,17 @@ pub(super) fn draw_boards_section(layout: &mut SidePaletteLayout, y: &mut f64) { }; let hint_layout = crate::ui_text::text_layout(ctx, hint_style, binding, None); let hint_width = hint_layout.ink_extents().width(); - let hint_x = x + content_width - hint_width - 4.0; + let chevron_reserve = ToolbarLayoutSpec::SIDE_COLLAPSE_CHEVRON_SIZE + 10.0; + let hint_x = x + content_width - hint_width - chevron_reserve; ctx.set_source_rgba(0.55, 0.58, 0.65, 0.9); hint_layout.show_at_baseline(ctx, hint_x, label_y); } + if snapshot.side_section_collapsed(ToolbarSideSection::Boards) { + *y += boards_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let boards_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let btn_h = if use_icons { ToolbarLayoutSpec::SIDE_ACTION_BUTTON_HEIGHT_ICON diff --git a/src/backend/wayland/toolbar/render/side_palette/colors.rs b/src/backend/wayland/toolbar/render/side_palette/colors.rs index 24b440eb..01eefab4 100644 --- a/src/backend/wayland/toolbar/render/side_palette/colors.rs +++ b/src/backend/wayland/toolbar/render/side_palette/colors.rs @@ -4,20 +4,23 @@ use super::{ColorSectionInfo, SidePaletteLayout}; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::config::Action; use crate::draw::{BLACK, BLUE, Color, GREEN, ORANGE, PINK, RED, WHITE, YELLOW}; -use crate::ui::toolbar::ToolbarEvent; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; use super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL}; -use super::super::widgets::{draw_group_card, draw_round_rect, draw_section_label}; +use super::super::widgets::{draw_group_card, draw_round_rect}; +use super::section_header::draw_collapsible_header; use helpers::{ ColorSwatch, ColorSwatchRowLayout, ColorSwatchToggle, draw_color_picker_area, draw_color_swatch_row, draw_hex_input, draw_preview_swatch_and_icon, }; -pub(super) fn draw_colors_section(layout: &mut SidePaletteLayout, y: &mut f64) -> ColorSectionInfo { +pub(super) fn draw_colors_section( + layout: &mut SidePaletteLayout, + y: &mut f64, +) -> Option { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -80,14 +83,20 @@ pub(super) fn draw_colors_section(layout: &mut SidePaletteLayout, y: &mut f64) - let colors_card_h = layout.spec.side_colors_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, colors_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, - "Colors", + ToolbarSideSection::Colors, + ToolbarSideSection::Colors.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, ); + if snapshot.side_section_collapsed(ToolbarSideSection::Colors) { + *y += colors_card_h + section_gap; + return None; + } + let hits = &mut layout.hits; let picker_y = *y + ToolbarLayoutSpec::SIDE_COLOR_PICKER_OFFSET_Y; let picker_w = content_width; draw_color_picker_area(ctx, hits, snapshot, x, picker_y, picker_w, picker_h); @@ -151,11 +160,11 @@ pub(super) fn draw_colors_section(layout: &mut SidePaletteLayout, y: &mut f64) - *y += colors_card_h + section_gap; - ColorSectionInfo { + Some(ColorSectionInfo { picker_y, picker_w, picker_h, - } + }) } pub(super) fn draw_preset_hover_highlight( diff --git a/src/backend/wayland/toolbar/render/side_palette/marker.rs b/src/backend/wayland/toolbar/render/side_palette/marker.rs index 5200dbc0..f708da44 100644 --- a/src/backend/wayland/toolbar/render/side_palette/marker.rs +++ b/src/backend/wayland/toolbar/render/side_palette/marker.rs @@ -9,13 +9,14 @@ use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::toolbar_icons; use crate::ui::toolbar::model::ToolbarSliderSpec; -use crate::ui::toolbar::{ToolContext, ToolbarEvent}; +use crate::ui::toolbar::{ToolContext, ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; +use super::section_header::draw_collapsible_header; + pub(super) fn draw_marker_opacity_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -33,7 +34,7 @@ pub(super) fn draw_marker_opacity_section(layout: &mut SidePaletteLayout, y: &mu return; } - let slider_card_h = ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT; + let slider_card_h = layout.spec.side_marker_opacity_height(snapshot); let btn_size = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; let nudge_icon_size = ToolbarLayoutSpec::SIDE_NUDGE_ICON_SIZE; let value_w = ToolbarLayoutSpec::SIDE_SLIDER_VALUE_WIDTH; @@ -42,14 +43,20 @@ pub(super) fn draw_marker_opacity_section(layout: &mut SidePaletteLayout, y: &mu let marker_slider_row_y = *y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; draw_group_card(ctx, card_x, *y, card_w, slider_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, + ToolbarSideSection::MarkerOpacity, "Marker opacity", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, ); + if snapshot.side_section_collapsed(ToolbarSideSection::MarkerOpacity) { + *y += slider_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let minus_x = x; let minus_hover = hover .map(|(hx, hy)| point_in_rect(hx, hy, minus_x, marker_slider_row_y, btn_size, btn_size)) diff --git a/src/backend/wayland/toolbar/render/side_palette/mod.rs b/src/backend/wayland/toolbar/render/side_palette/mod.rs index 56693819..5a87dbf3 100644 --- a/src/backend/wayland/toolbar/render/side_palette/mod.rs +++ b/src/backend/wayland/toolbar/render/side_palette/mod.rs @@ -7,6 +7,7 @@ mod header; mod marker; mod pages; mod presets; +mod section_header; mod session; mod settings; mod step; @@ -94,7 +95,7 @@ pub fn render_side_palette( // Color section: only show when the tool needs color let colors_info = if tool_context.needs_color { - Some(colors::draw_colors_section(&mut layout, &mut y)) + colors::draw_colors_section(&mut layout, &mut y) } else { None }; diff --git a/src/backend/wayland/toolbar/render/side_palette/pages.rs b/src/backend/wayland/toolbar/render/side_palette/pages.rs index 0bb73a54..b9d61e82 100644 --- a/src/backend/wayland/toolbar/render/side_palette/pages.rs +++ b/src/backend/wayland/toolbar/render/side_palette/pages.rs @@ -5,17 +5,17 @@ use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::backend::wayland::toolbar::rows::{grid_layout, row_item_width}; use crate::toolbar_icons; -use crate::ui::toolbar::ToolbarEvent; use crate::ui::toolbar::model::toolbar_pages_model; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; use super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL}; use super::super::widgets::*; +use super::section_header::draw_collapsible_header; pub(super) fn draw_pages_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -36,14 +36,20 @@ pub(super) fn draw_pages_section(layout: &mut SidePaletteLayout, y: &mut f64) { let pages_card_h = layout.spec.side_pages_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, pages_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, - "Pages", + ToolbarSideSection::Pages, + ToolbarSideSection::Pages.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if snapshot.side_section_collapsed(ToolbarSideSection::Pages) { + *y += pages_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let pages_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let btn_h = if use_icons { ToolbarLayoutSpec::SIDE_ACTION_BUTTON_HEIGHT_ICON diff --git a/src/backend/wayland/toolbar/render/side_palette/presets/header.rs b/src/backend/wayland/toolbar/render/side_palette/presets/header.rs index 3819058a..f356db04 100644 --- a/src/backend/wayland/toolbar/render/side_palette/presets/header.rs +++ b/src/backend/wayland/toolbar/render/side_palette/presets/header.rs @@ -1,8 +1,9 @@ use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui_text::{UiTextStyle, text_layout}; use super::super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL}; -use super::super::super::widgets::draw_section_label; +use super::super::section_header::draw_collapsible_header; use super::SidePaletteLayout; pub(super) fn draw_presets_header( @@ -14,7 +15,6 @@ pub(super) fn draw_presets_header( ) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let x = layout.x; let label_style = UiTextStyle { family: FONT_FAMILY_DEFAULT, slant: cairo::FontSlant::Normal, @@ -22,12 +22,13 @@ pub(super) fn draw_presets_header( size: FONT_SIZE_LABEL, }; - draw_section_label( - ctx, + draw_collapsible_header( + layout, + section_y, label_style, - x, - section_y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, - "Presets", + ToolbarSideSection::Presets, + ToolbarSideSection::Presets.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, ); let apply_hint = { @@ -54,7 +55,8 @@ pub(super) fn draw_presets_header( }; let layout = text_layout(ctx, hint_style, &hint, None); let ext = layout.ink_extents(); - let hint_x = card_x + card_w - ext.width() - 8.0 - ext.x_bearing(); + let chevron_reserve = ToolbarLayoutSpec::SIDE_COLLAPSE_CHEVRON_SIZE + 10.0; + let hint_x = card_x + card_w - ext.width() - chevron_reserve - ext.x_bearing(); let hint_y = section_y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y; ctx.set_source_rgba(0.7, 0.7, 0.75, 0.8); layout.show_at_baseline(ctx, hint_x, hint_y); diff --git a/src/backend/wayland/toolbar/render/side_palette/presets/mod.rs b/src/backend/wayland/toolbar/render/side_palette/presets/mod.rs index 6ab2942e..3563a5f3 100644 --- a/src/backend/wayland/toolbar/render/side_palette/presets/mod.rs +++ b/src/backend/wayland/toolbar/render/side_palette/presets/mod.rs @@ -6,6 +6,7 @@ mod widgets; use super::SidePaletteLayout; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::draw::Color; +use crate::ui::toolbar::ToolbarSideSection; use super::super::widgets::draw_group_card; use header::draw_presets_header; @@ -25,9 +26,13 @@ pub(super) fn draw_presets_section(layout: &mut SidePaletteLayout, y: &mut f64) let mut hover_preset_color: Option = None; - let presets_card_h = ToolbarLayoutSpec::SIDE_PRESET_CARD_HEIGHT; + let presets_card_h = layout.spec.side_presets_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, presets_card_h); draw_presets_header(layout, *y, card_x, card_w, slot_count); + if snapshot.side_section_collapsed(ToolbarSideSection::Presets) { + *y += presets_card_h + section_gap; + return None; + } let slot_size = ToolbarLayoutSpec::SIDE_PRESET_SLOT_SIZE; let slot_gap = ToolbarLayoutSpec::SIDE_PRESET_SLOT_GAP; diff --git a/src/backend/wayland/toolbar/render/side_palette/section_header.rs b/src/backend/wayland/toolbar/render/side_palette/section_header.rs new file mode 100644 index 00000000..8bbde0b8 --- /dev/null +++ b/src/backend/wayland/toolbar/render/side_palette/section_header.rs @@ -0,0 +1,78 @@ +use super::SidePaletteLayout; +use crate::backend::wayland::toolbar::events::HitKind; +use crate::backend::wayland::toolbar::hit::HitRegion; +use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; +use crate::ui_text::UiTextStyle; + +use super::super::widgets::constants::{COLOR_LABEL_SECTION, set_color}; +use super::super::widgets::{draw_section_label, point_in_rect}; + +pub(super) fn draw_collapsible_header( + layout: &mut SidePaletteLayout, + y: f64, + label_style: UiTextStyle<'static>, + section: ToolbarSideSection, + label: &str, + label_offset_y: f64, +) { + let collapsed = layout.snapshot.side_section_collapsed(section); + let hit_h = ToolbarLayoutSpec::SIDE_COLLAPSE_HEADER_HIT_HEIGHT; + let hover = layout + .hover + .map(|(hx, hy)| point_in_rect(hx, hy, layout.card_x, y, layout.card_w, hit_h)) + .unwrap_or(false); + + draw_section_label(layout.ctx, label_style, layout.x, y + label_offset_y, label); + + let size = ToolbarLayoutSpec::SIDE_COLLAPSE_CHEVRON_SIZE; + let chevron_x = layout.x + layout.content_width - size; + let chevron_y = y + (hit_h - size) / 2.0; + draw_header_chevron(layout.ctx, chevron_x, chevron_y, size, collapsed, hover); + + layout.hits.push(HitRegion { + rect: (layout.card_x, y, layout.card_w, hit_h), + event: ToolbarEvent::ToggleSideSectionCollapsed(section, !collapsed), + kind: HitKind::Click, + tooltip: Some(format!( + "{} {}", + if collapsed { "Expand" } else { "Collapse" }, + label + )), + }); +} + +fn draw_header_chevron( + ctx: &cairo::Context, + x: f64, + y: f64, + size: f64, + collapsed: bool, + hover: bool, +) { + set_color( + ctx, + ( + COLOR_LABEL_SECTION.0, + COLOR_LABEL_SECTION.1, + COLOR_LABEL_SECTION.2, + if hover { 1.0 } else { COLOR_LABEL_SECTION.3 }, + ), + ); + ctx.set_line_width(1.6); + ctx.set_line_cap(cairo::LineCap::Round); + ctx.set_line_join(cairo::LineJoin::Round); + let margin = size * 0.28; + if collapsed { + let mid_y = y + size / 2.0; + ctx.move_to(x + margin, y + margin); + ctx.line_to(x + size - margin, mid_y); + ctx.line_to(x + margin, y + size - margin); + } else { + let mid_x = x + size / 2.0; + ctx.move_to(x + margin, y + margin); + ctx.line_to(mid_x, y + size - margin); + ctx.line_to(x + size - margin, y + margin); + } + let _ = ctx.stroke(); +} diff --git a/src/backend/wayland/toolbar/render/side_palette/session.rs b/src/backend/wayland/toolbar/render/side_palette/session.rs index 66edb273..419a8c40 100644 --- a/src/backend/wayland/toolbar/render/side_palette/session.rs +++ b/src/backend/wayland/toolbar/render/side_palette/session.rs @@ -13,9 +13,11 @@ use super::super::widgets::constants::{ COLOR_TEXT_DISABLED, COLOR_TEXT_PRIMARY, FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL, set_color, }; use super::super::widgets::{ - draw_button, draw_destructive_button, draw_group_card, draw_label_center, draw_section_label, - point_in_rect, set_icon_color, + draw_button, draw_destructive_button, draw_group_card, draw_label_center, point_in_rect, + set_icon_color, }; +use super::section_header::draw_collapsible_header; +use crate::ui::toolbar::ToolbarSideSection; const SESSION_BUTTON_ICON_SIZE: f64 = 12.0; const SESSION_BUTTON_ICON_GAP: f64 = 4.0; @@ -52,13 +54,21 @@ pub(super) fn draw_session_section(layout: &mut SidePaletteLayout, y: &mut f64) size: 9.5, }; - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - layout.x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, - "Session", + ToolbarSideSection::Session, + ToolbarSideSection::Session.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if layout + .snapshot + .side_section_collapsed(ToolbarSideSection::Session) + { + *y += card_h + layout.section_gap; + return; + } let meta_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; draw_text_baseline( diff --git a/src/backend/wayland/toolbar/render/side_palette/settings.rs b/src/backend/wayland/toolbar/render/side_palette/settings.rs index f64a6991..4f8b965f 100644 --- a/src/backend/wayland/toolbar/render/side_palette/settings.rs +++ b/src/backend/wayland/toolbar/render/side_palette/settings.rs @@ -4,16 +4,17 @@ use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::backend::wayland::toolbar::rows::{grid_layout, row_item_width}; use crate::toolbar_icons; +use crate::ui::toolbar::ToolbarSideSection; use crate::ui::toolbar::model::{ToolbarActivation, ToolbarIcon, ToolbarSettingsModel}; use crate::ui_text::UiTextStyle; use super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_LABEL}; use super::super::widgets::*; +use super::section_header::draw_collapsible_header; pub(super) fn draw_settings_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -40,14 +41,20 @@ pub(super) fn draw_settings_section(layout: &mut SidePaletteLayout, y: &mut f64) let settings_card_h = layout.spec.side_settings_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, settings_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, - "Settings", + ToolbarSideSection::Settings, + ToolbarSideSection::Settings.label(), + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if snapshot.side_section_collapsed(ToolbarSideSection::Settings) { + *y += settings_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let toggle_h = ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT; let toggle_gap = ToolbarLayoutSpec::SIDE_TOGGLE_GAP; let toggles = settings_model.toggles(); diff --git a/src/backend/wayland/toolbar/render/side_palette/step/mod.rs b/src/backend/wayland/toolbar/render/side_palette/step/mod.rs index 091eed58..e5fcbc2a 100644 --- a/src/backend/wayland/toolbar/render/side_palette/step/mod.rs +++ b/src/backend/wayland/toolbar/render/side_palette/step/mod.rs @@ -7,13 +7,14 @@ use crate::backend::wayland::toolbar::events::HitKind; use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::input::ToolbarDrawerTab; -use crate::ui::toolbar::ToolbarEvent; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; +use super::section_header::draw_collapsible_header; + pub(super) fn draw_step_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -42,22 +43,22 @@ pub(super) fn draw_step_section(layout: &mut SidePaletteLayout, y: &mut f64) { } else { 0.0 }; - let delay_sliders_h = if snapshot.show_delay_sliders { - ToolbarLayoutSpec::SIDE_DELAY_SECTION_HEIGHT - } else { - 0.0 - }; - let custom_card_h = - ToolbarLayoutSpec::SIDE_STEP_HEADER_HEIGHT + toggles_h + custom_content_h + delay_sliders_h; + let custom_card_h = layout.spec.side_step_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, custom_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, + ToolbarSideSection::StepUndo, "Step Undo/Redo", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if snapshot.side_section_collapsed(ToolbarSideSection::StepUndo) { + *y += custom_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let custom_toggle_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let toggle_w = content_width; diff --git a/src/backend/wayland/toolbar/render/side_palette/step_marker.rs b/src/backend/wayland/toolbar/render/side_palette/step_marker.rs index a2ef7471..9268c62d 100644 --- a/src/backend/wayland/toolbar/render/side_palette/step_marker.rs +++ b/src/backend/wayland/toolbar/render/side_palette/step_marker.rs @@ -6,13 +6,14 @@ use super::SidePaletteLayout; use crate::backend::wayland::toolbar::events::HitKind; use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; -use crate::ui::toolbar::{ToolContext, ToolbarEvent}; +use crate::ui::toolbar::{ToolContext, ToolbarEvent, ToolbarSideSection}; use crate::ui_text::{UiTextStyle, text_layout}; +use super::section_header::draw_collapsible_header; + pub(super) fn draw_step_marker_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -36,23 +37,30 @@ pub(super) fn draw_step_marker_section(layout: &mut SidePaletteLayout, y: &mut f return; } - let card_h = ToolbarLayoutSpec::SIDE_TOGGLE_CARD_HEIGHT_WITH_RESET; + let card_h = layout.spec.side_step_markers_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, + ToolbarSideSection::StepMarkers, "Step markers", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); let hint = format!("Next: {}", snapshot.step_marker_next); let layout_text = text_layout(ctx, hint_style, &hint, None); let ext = layout_text.ink_extents(); - let hint_x = card_x + card_w - ext.width() - 8.0 - ext.x_bearing(); + let chevron_reserve = ToolbarLayoutSpec::SIDE_COLLAPSE_CHEVRON_SIZE + 10.0; + let hint_x = card_x + card_w - ext.width() - chevron_reserve - ext.x_bearing(); let hint_y = *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL; set_color(ctx, COLOR_LABEL_HINT); layout_text.show_at_baseline(ctx, hint_x, hint_y); + if snapshot.side_section_collapsed(ToolbarSideSection::StepMarkers) { + *y += card_h + section_gap; + return; + } + let hits = &mut layout.hits; let reset_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; let reset_h = ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT; let reset_hover = hover diff --git a/src/backend/wayland/toolbar/render/side_palette/text.rs b/src/backend/wayland/toolbar/render/side_palette/text.rs index 80df3233..790316f9 100644 --- a/src/backend/wayland/toolbar/render/side_palette/text.rs +++ b/src/backend/wayland/toolbar/render/side_palette/text.rs @@ -9,14 +9,15 @@ use crate::backend::wayland::toolbar::hit::HitRegion; use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::draw::FontDescriptor; use crate::toolbar_icons; -use crate::ui::toolbar::ToolbarEvent; use crate::ui::toolbar::model::ToolbarSliderSpec; +use crate::ui::toolbar::{ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; +use super::section_header::draw_collapsible_header; + pub(super) fn draw_text_controls_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -37,7 +38,7 @@ pub(super) fn draw_text_controls_section(layout: &mut SidePaletteLayout, y: &mut return; } - let slider_card_h = ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT; + let slider_card_h = layout.spec.side_text_size_height(snapshot); let btn_size = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; let nudge_icon_size = ToolbarLayoutSpec::SIDE_NUDGE_ICON_SIZE; let value_w = ToolbarLayoutSpec::SIDE_SLIDER_VALUE_WIDTH; @@ -45,123 +46,135 @@ pub(super) fn draw_text_controls_section(layout: &mut SidePaletteLayout, y: &mut let knob_r = ToolbarLayoutSpec::SIDE_TRACK_KNOB_RADIUS; draw_group_card(ctx, card_x, *y, card_w, slider_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, + ToolbarSideSection::TextSize, "Text size", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, ); + if snapshot.side_section_collapsed(ToolbarSideSection::TextSize) { + *y += slider_card_h + section_gap; + } else { + let hits = &mut layout.hits; - let font_size_spec = ToolbarSliderSpec::FONT_SIZE; - let fs_min = font_size_spec.min; - let fs_max = font_size_spec.max; - let fs_step = font_size_spec.step.unwrap_or(2.0); - let fs_slider_row_y = *y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; - - let fs_minus_x = x; - let fs_minus_hover = hover - .map(|(hx, hy)| point_in_rect(hx, hy, fs_minus_x, fs_slider_row_y, btn_size, btn_size)) - .unwrap_or(false); - draw_button( - ctx, - fs_minus_x, - fs_slider_row_y, - btn_size, - btn_size, - false, - fs_minus_hover, - ); - set_icon_color(ctx, fs_minus_hover); - toolbar_icons::draw_icon_minus( - ctx, - fs_minus_x + (btn_size - nudge_icon_size) / 2.0, - fs_slider_row_y + (btn_size - nudge_icon_size) / 2.0, - nudge_icon_size, - ); - hits.push(HitRegion { - rect: (fs_minus_x, fs_slider_row_y, btn_size, btn_size), - event: ToolbarEvent::SetFontSize((snapshot.font_size - fs_step).max(fs_min)), - kind: HitKind::Click, - tooltip: Some("Decrease font size".to_string()), - }); - - let fs_plus_x = width - x - btn_size - value_w - 4.0; - let fs_plus_hover = hover - .map(|(hx, hy)| point_in_rect(hx, hy, fs_plus_x, fs_slider_row_y, btn_size, btn_size)) - .unwrap_or(false); - draw_button( - ctx, - fs_plus_x, - fs_slider_row_y, - btn_size, - btn_size, - false, - fs_plus_hover, - ); - set_icon_color(ctx, fs_plus_hover); - toolbar_icons::draw_icon_plus( - ctx, - fs_plus_x + (btn_size - nudge_icon_size) / 2.0, - fs_slider_row_y + (btn_size - nudge_icon_size) / 2.0, - nudge_icon_size, - ); - hits.push(HitRegion { - rect: (fs_plus_x, fs_slider_row_y, btn_size, btn_size), - event: ToolbarEvent::SetFontSize((snapshot.font_size + fs_step).min(fs_max)), - kind: HitKind::Click, - tooltip: Some("Increase font size".to_string()), - }); - - let fs_track_x = fs_minus_x + btn_size + SPACING_STD; - let fs_track_w = fs_plus_x - fs_track_x - SPACING_STD; - let fs_track_y = fs_slider_row_y + (btn_size - track_h) / 2.0; - let fs_knob_x = - font_size_spec.knob_center_x(fs_track_x, fs_track_w, knob_r, snapshot.font_size); - - set_color(ctx, COLOR_TRACK_BACKGROUND); - draw_round_rect(ctx, fs_track_x, fs_track_y, fs_track_w, track_h, 4.0); - let _ = ctx.fill(); - set_color(ctx, COLOR_TRACK_KNOB); - ctx.arc( - fs_knob_x, - fs_track_y + track_h / 2.0, - knob_r, - 0.0, - std::f64::consts::PI * 2.0, - ); - let _ = ctx.fill(); - - hits.push(HitRegion { - rect: (fs_track_x, fs_track_y - 6.0, fs_track_w, track_h + 12.0), - event: ToolbarEvent::SetFontSize(snapshot.font_size), - kind: HitKind::DragSetFontSize, - tooltip: None, - }); - - let fs_text = format!("{:.0}pt", snapshot.font_size); - draw_label_center( - ctx, - label_style, - width - x - value_w, - fs_slider_row_y, - value_w, - btn_size, - &fs_text, - ); + let font_size_spec = ToolbarSliderSpec::FONT_SIZE; + let fs_min = font_size_spec.min; + let fs_max = font_size_spec.max; + let fs_step = font_size_spec.step.unwrap_or(2.0); + let fs_slider_row_y = *y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; + + let fs_minus_x = x; + let fs_minus_hover = hover + .map(|(hx, hy)| point_in_rect(hx, hy, fs_minus_x, fs_slider_row_y, btn_size, btn_size)) + .unwrap_or(false); + draw_button( + ctx, + fs_minus_x, + fs_slider_row_y, + btn_size, + btn_size, + false, + fs_minus_hover, + ); + set_icon_color(ctx, fs_minus_hover); + toolbar_icons::draw_icon_minus( + ctx, + fs_minus_x + (btn_size - nudge_icon_size) / 2.0, + fs_slider_row_y + (btn_size - nudge_icon_size) / 2.0, + nudge_icon_size, + ); + hits.push(HitRegion { + rect: (fs_minus_x, fs_slider_row_y, btn_size, btn_size), + event: ToolbarEvent::SetFontSize((snapshot.font_size - fs_step).max(fs_min)), + kind: HitKind::Click, + tooltip: Some("Decrease font size".to_string()), + }); + + let fs_plus_x = width - x - btn_size - value_w - 4.0; + let fs_plus_hover = hover + .map(|(hx, hy)| point_in_rect(hx, hy, fs_plus_x, fs_slider_row_y, btn_size, btn_size)) + .unwrap_or(false); + draw_button( + ctx, + fs_plus_x, + fs_slider_row_y, + btn_size, + btn_size, + false, + fs_plus_hover, + ); + set_icon_color(ctx, fs_plus_hover); + toolbar_icons::draw_icon_plus( + ctx, + fs_plus_x + (btn_size - nudge_icon_size) / 2.0, + fs_slider_row_y + (btn_size - nudge_icon_size) / 2.0, + nudge_icon_size, + ); + hits.push(HitRegion { + rect: (fs_plus_x, fs_slider_row_y, btn_size, btn_size), + event: ToolbarEvent::SetFontSize((snapshot.font_size + fs_step).min(fs_max)), + kind: HitKind::Click, + tooltip: Some("Increase font size".to_string()), + }); - *y += slider_card_h + section_gap; + let fs_track_x = fs_minus_x + btn_size + SPACING_STD; + let fs_track_w = fs_plus_x - fs_track_x - SPACING_STD; + let fs_track_y = fs_slider_row_y + (btn_size - track_h) / 2.0; + let fs_knob_x = + font_size_spec.knob_center_x(fs_track_x, fs_track_w, knob_r, snapshot.font_size); - let font_card_h = ToolbarLayoutSpec::SIDE_FONT_CARD_HEIGHT; + set_color(ctx, COLOR_TRACK_BACKGROUND); + draw_round_rect(ctx, fs_track_x, fs_track_y, fs_track_w, track_h, 4.0); + let _ = ctx.fill(); + set_color(ctx, COLOR_TRACK_KNOB); + ctx.arc( + fs_knob_x, + fs_track_y + track_h / 2.0, + knob_r, + 0.0, + std::f64::consts::PI * 2.0, + ); + let _ = ctx.fill(); + + hits.push(HitRegion { + rect: (fs_track_x, fs_track_y - 6.0, fs_track_w, track_h + 12.0), + event: ToolbarEvent::SetFontSize(snapshot.font_size), + kind: HitKind::DragSetFontSize, + tooltip: None, + }); + + let fs_text = format!("{:.0}pt", snapshot.font_size); + draw_label_center( + ctx, + label_style, + width - x - value_w, + fs_slider_row_y, + value_w, + btn_size, + &fs_text, + ); + + *y += slider_card_h + section_gap; + } + + let font_card_h = layout.spec.side_font_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, font_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, + ToolbarSideSection::Font, "Font", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if snapshot.side_section_collapsed(ToolbarSideSection::Font) { + *y += font_card_h + section_gap; + return; + } + let hits = &mut layout.hits; let font_btn_h = ToolbarLayoutSpec::SIDE_FONT_BUTTON_HEIGHT; let font_gap = ToolbarLayoutSpec::SIDE_FONT_BUTTON_GAP; let font_btn_w = (content_width - font_gap) / 2.0; diff --git a/src/backend/wayland/toolbar/render/side_palette/thickness.rs b/src/backend/wayland/toolbar/render/side_palette/thickness.rs index 365e3cf2..908ba8ea 100644 --- a/src/backend/wayland/toolbar/render/side_palette/thickness.rs +++ b/src/backend/wayland/toolbar/render/side_palette/thickness.rs @@ -12,13 +12,14 @@ use crate::config::Action; use crate::input::EraserMode; use crate::toolbar_icons; use crate::ui::toolbar::model::ToolbarSliderSpec; -use crate::ui::toolbar::{ToolContext, ToolbarEvent}; +use crate::ui::toolbar::{ToolContext, ToolbarEvent, ToolbarSideSection}; use crate::ui_text::UiTextStyle; +use super::section_header::draw_collapsible_header; + pub(super) fn draw_thickness_section(layout: &mut SidePaletteLayout, y: &mut f64) { let ctx = layout.ctx; let snapshot = layout.snapshot; - let hits = &mut layout.hits; let hover = layout.hover; let x = layout.x; let card_x = layout.card_x; @@ -34,167 +35,183 @@ pub(super) fn draw_thickness_section(layout: &mut SidePaletteLayout, y: &mut f64 }; let tool_context = ToolContext::from_snapshot(snapshot); - let slider_card_h = ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT; + let slider_card_h = layout.spec.side_thickness_height(snapshot); draw_group_card(ctx, card_x, *y, card_w, slider_card_h); let thickness_label = tool_context.thickness_label; - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, + ToolbarSideSection::Thickness, thickness_label, + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, ); + if snapshot.side_section_collapsed(ToolbarSideSection::Thickness) { + *y += slider_card_h + section_gap; + } else { + let hits = &mut layout.hits; - let btn_size = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; - let nudge_icon_size = ToolbarLayoutSpec::SIDE_NUDGE_ICON_SIZE; - let value_w = ToolbarLayoutSpec::SIDE_SLIDER_VALUE_WIDTH; - let thickness_slider_row_y = *y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; - let track_h = ToolbarLayoutSpec::SIDE_TRACK_HEIGHT; - let knob_r = ToolbarLayoutSpec::SIDE_TRACK_KNOB_RADIUS; - let thickness_spec = ToolbarSliderSpec::THICKNESS; - let nudge_step = thickness_spec.step.unwrap_or(1.0); + let btn_size = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; + let nudge_icon_size = ToolbarLayoutSpec::SIDE_NUDGE_ICON_SIZE; + let value_w = ToolbarLayoutSpec::SIDE_SLIDER_VALUE_WIDTH; + let thickness_slider_row_y = *y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; + let track_h = ToolbarLayoutSpec::SIDE_TRACK_HEIGHT; + let knob_r = ToolbarLayoutSpec::SIDE_TRACK_KNOB_RADIUS; + let thickness_spec = ToolbarSliderSpec::THICKNESS; + let nudge_step = thickness_spec.step.unwrap_or(1.0); - let minus_x = x; - let minus_hover = hover - .map(|(hx, hy)| point_in_rect(hx, hy, minus_x, thickness_slider_row_y, btn_size, btn_size)) - .unwrap_or(false); - draw_button( - ctx, - minus_x, - thickness_slider_row_y, - btn_size, - btn_size, - false, - minus_hover, - ); - set_icon_color(ctx, minus_hover); - toolbar_icons::draw_icon_minus( - ctx, - minus_x + (btn_size - nudge_icon_size) / 2.0, - thickness_slider_row_y + (btn_size - nudge_icon_size) / 2.0, - nudge_icon_size, - ); - hits.push(HitRegion { - rect: (minus_x, thickness_slider_row_y, btn_size, btn_size), - event: ToolbarEvent::NudgeThickness(-nudge_step), - kind: HitKind::Click, - tooltip: Some("Decrease thickness".to_string()), - }); + let minus_x = x; + let minus_hover = hover + .map(|(hx, hy)| { + point_in_rect(hx, hy, minus_x, thickness_slider_row_y, btn_size, btn_size) + }) + .unwrap_or(false); + draw_button( + ctx, + minus_x, + thickness_slider_row_y, + btn_size, + btn_size, + false, + minus_hover, + ); + set_icon_color(ctx, minus_hover); + toolbar_icons::draw_icon_minus( + ctx, + minus_x + (btn_size - nudge_icon_size) / 2.0, + thickness_slider_row_y + (btn_size - nudge_icon_size) / 2.0, + nudge_icon_size, + ); + hits.push(HitRegion { + rect: (minus_x, thickness_slider_row_y, btn_size, btn_size), + event: ToolbarEvent::NudgeThickness(-nudge_step), + kind: HitKind::Click, + tooltip: Some("Decrease thickness".to_string()), + }); - let plus_x = width - x - btn_size - value_w - 4.0; - let plus_hover = hover - .map(|(hx, hy)| point_in_rect(hx, hy, plus_x, thickness_slider_row_y, btn_size, btn_size)) - .unwrap_or(false); - draw_button( - ctx, - plus_x, - thickness_slider_row_y, - btn_size, - btn_size, - false, - plus_hover, - ); - set_icon_color(ctx, plus_hover); - toolbar_icons::draw_icon_plus( - ctx, - plus_x + (btn_size - nudge_icon_size) / 2.0, - thickness_slider_row_y + (btn_size - nudge_icon_size) / 2.0, - nudge_icon_size, - ); - hits.push(HitRegion { - rect: (plus_x, thickness_slider_row_y, btn_size, btn_size), - event: ToolbarEvent::NudgeThickness(nudge_step), - kind: HitKind::Click, - tooltip: Some("Increase thickness".to_string()), - }); + let plus_x = width - x - btn_size - value_w - 4.0; + let plus_hover = hover + .map(|(hx, hy)| { + point_in_rect(hx, hy, plus_x, thickness_slider_row_y, btn_size, btn_size) + }) + .unwrap_or(false); + draw_button( + ctx, + plus_x, + thickness_slider_row_y, + btn_size, + btn_size, + false, + plus_hover, + ); + set_icon_color(ctx, plus_hover); + toolbar_icons::draw_icon_plus( + ctx, + plus_x + (btn_size - nudge_icon_size) / 2.0, + thickness_slider_row_y + (btn_size - nudge_icon_size) / 2.0, + nudge_icon_size, + ); + hits.push(HitRegion { + rect: (plus_x, thickness_slider_row_y, btn_size, btn_size), + event: ToolbarEvent::NudgeThickness(nudge_step), + kind: HitKind::Click, + tooltip: Some("Increase thickness".to_string()), + }); - let track_x = minus_x + btn_size + SPACING_STD; - let track_w = plus_x - track_x - SPACING_STD; - let thickness_track_y = thickness_slider_row_y + (btn_size - track_h) / 2.0; - let knob_x = thickness_spec.knob_center_x(track_x, track_w, knob_r, snapshot.thickness); + let track_x = minus_x + btn_size + SPACING_STD; + let track_w = plus_x - track_x - SPACING_STD; + let thickness_track_y = thickness_slider_row_y + (btn_size - track_h) / 2.0; + let knob_x = thickness_spec.knob_center_x(track_x, track_w, knob_r, snapshot.thickness); - set_color(ctx, COLOR_TRACK_BACKGROUND); - draw_round_rect(ctx, track_x, thickness_track_y, track_w, track_h, 4.0); - let _ = ctx.fill(); - set_color(ctx, COLOR_TRACK_KNOB); - ctx.arc( - knob_x, - thickness_track_y + track_h / 2.0, - knob_r, - 0.0, - std::f64::consts::PI * 2.0, - ); - let _ = ctx.fill(); + set_color(ctx, COLOR_TRACK_BACKGROUND); + draw_round_rect(ctx, track_x, thickness_track_y, track_w, track_h, 4.0); + let _ = ctx.fill(); + set_color(ctx, COLOR_TRACK_KNOB); + ctx.arc( + knob_x, + thickness_track_y + track_h / 2.0, + knob_r, + 0.0, + std::f64::consts::PI * 2.0, + ); + let _ = ctx.fill(); - hits.push(HitRegion { - rect: (track_x, thickness_track_y - 6.0, track_w, track_h + 12.0), - event: ToolbarEvent::SetThickness(snapshot.thickness), - kind: HitKind::DragSetThickness { - min: thickness_spec.min, - max: thickness_spec.max, - }, - tooltip: None, - }); + hits.push(HitRegion { + rect: (track_x, thickness_track_y - 6.0, track_w, track_h + 12.0), + event: ToolbarEvent::SetThickness(snapshot.thickness), + kind: HitKind::DragSetThickness { + min: thickness_spec.min, + max: thickness_spec.max, + }, + tooltip: None, + }); - let thickness_text = format!("{:.0}px", snapshot.thickness); - let value_x = width - x - value_w; - draw_label_center( - ctx, - label_style, - value_x, - thickness_slider_row_y, - value_w, - btn_size, - &thickness_text, - ); - *y += slider_card_h + section_gap; + let thickness_text = format!("{:.0}px", snapshot.thickness); + let value_x = width - x - value_w; + draw_label_center( + ctx, + label_style, + value_x, + thickness_slider_row_y, + value_w, + btn_size, + &thickness_text, + ); + *y += slider_card_h + section_gap; + } if tool_context.show_eraser_mode { - let eraser_card_h = ToolbarLayoutSpec::SIDE_ERASER_MODE_CARD_HEIGHT; + let eraser_card_h = layout.spec.side_eraser_mode_height(snapshot); let toggle_h = ToolbarLayoutSpec::SIDE_TOGGLE_HEIGHT; let toggle_w = content_width; draw_group_card(ctx, card_x, *y, card_w, eraser_card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, + ToolbarSideSection::EraserMode, "Eraser mode", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_TALL, ); + if snapshot.side_section_collapsed(ToolbarSideSection::EraserMode) { + *y += eraser_card_h + section_gap; + } else { + let hits = &mut layout.hits; - let toggle_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; - let toggle_hover = hover - .map(|(hx, hy)| point_in_rect(hx, hy, x, toggle_y, toggle_w, toggle_h)) - .unwrap_or(false); - let stroke_active = snapshot.eraser_mode == EraserMode::Stroke; - draw_checkbox( - ctx, - x, - toggle_y, - toggle_w, - toggle_h, - stroke_active, - toggle_hover, - label_style, - "Erase by stroke", - ); - let toggle_tooltip = format_binding_label( - "Erase by stroke", - snapshot - .binding_hints - .binding_for_action(Action::ToggleEraserMode), - ); - hits.push(HitRegion { - rect: (x, toggle_y, toggle_w, toggle_h), - event: ToolbarEvent::SetEraserMode(if stroke_active { - EraserMode::Brush - } else { - EraserMode::Stroke - }), - kind: HitKind::Click, - tooltip: Some(toggle_tooltip), - }); - *y += eraser_card_h + section_gap; + let toggle_y = *y + ToolbarLayoutSpec::SIDE_SECTION_TOGGLE_OFFSET_Y; + let toggle_hover = hover + .map(|(hx, hy)| point_in_rect(hx, hy, x, toggle_y, toggle_w, toggle_h)) + .unwrap_or(false); + let stroke_active = snapshot.eraser_mode == EraserMode::Stroke; + draw_checkbox( + ctx, + x, + toggle_y, + toggle_w, + toggle_h, + stroke_active, + toggle_hover, + label_style, + "Erase by stroke", + ); + let toggle_tooltip = format_binding_label( + "Erase by stroke", + snapshot + .binding_hints + .binding_for_action(Action::ToggleEraserMode), + ); + hits.push(HitRegion { + rect: (x, toggle_y, toggle_w, toggle_h), + event: ToolbarEvent::SetEraserMode(if stroke_active { + EraserMode::Brush + } else { + EraserMode::Stroke + }), + kind: HitKind::Click, + tooltip: Some(toggle_tooltip), + }); + *y += eraser_card_h + section_gap; + } } if tool_context.show_polygon_sides_control { @@ -205,7 +222,7 @@ pub(super) fn draw_thickness_section(layout: &mut SidePaletteLayout, y: &mut f64 fn draw_polygon_sides_section( layout: &mut SidePaletteLayout, y: &mut f64, - label_style: UiTextStyle, + label_style: UiTextStyle<'static>, ) { let ctx = layout.ctx; let snapshot = layout.snapshot; @@ -216,20 +233,25 @@ fn draw_polygon_sides_section( let content_width = layout.content_width; let section_gap = layout.section_gap; let width = layout.width; - let card_h = ToolbarLayoutSpec::SIDE_SLIDER_CARD_HEIGHT; + let card_h = layout.spec.side_polygon_sides_height(snapshot); let btn_size = ToolbarLayoutSpec::SIDE_NUDGE_SIZE; let icon_size = ToolbarLayoutSpec::SIDE_NUDGE_ICON_SIZE; let value_w = ToolbarLayoutSpec::SIDE_SLIDER_VALUE_WIDTH; let row_y = *y + ToolbarLayoutSpec::SIDE_SLIDER_ROW_OFFSET; draw_group_card(ctx, card_x, *y, card_w, card_h); - draw_section_label( - ctx, + draw_collapsible_header( + layout, + *y, label_style, - x, - *y + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, + ToolbarSideSection::PolygonSides, "Sides", + ToolbarLayoutSpec::SIDE_SECTION_LABEL_OFFSET_Y, ); + if snapshot.side_section_collapsed(ToolbarSideSection::PolygonSides) { + *y += card_h + section_gap; + return; + } let minus_x = x; let minus_hover = hover diff --git a/src/input/state/core/base/state/init.rs b/src/input/state/core/base/state/init.rs index 7f6a3a6b..02466fca 100644 --- a/src/input/state/core/base/state/init.rs +++ b/src/input/state/core/base/state/init.rs @@ -241,6 +241,7 @@ impl InputState { show_text_controls: true, context_aware_ui: true, show_settings_section: true, + toolbar_collapsed_side_sections: std::collections::BTreeSet::new(), preset_slot_count: PRESET_SLOTS_MAX, presets: vec![None; PRESET_SLOTS_MAX], active_preset_slot: None, diff --git a/src/input/state/core/base/state/structs.rs b/src/input/state/core/base/state/structs.rs index 8d0d8157..ab320749 100644 --- a/src/input/state/core/base/state/structs.rs +++ b/src/input/state/core/base/state/structs.rs @@ -34,8 +34,9 @@ use crate::input::{ }; use crate::render_profiles::RenderProfileSet; use crate::session::SessionOptions; +use crate::ui::toolbar::ToolbarSideSection; use crate::util::Rect; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::path::PathBuf; use std::time::Instant; @@ -400,6 +401,8 @@ pub struct InputState { pub context_aware_ui: bool, /// Whether to show the Settings section pub show_settings_section: bool, + /// Side drawer sections whose body content is collapsed for this runtime. + pub toolbar_collapsed_side_sections: BTreeSet, /// Number of preset slots to display pub preset_slot_count: usize, /// Preset slots for quick tool switching diff --git a/src/ui/toolbar/apply/layout.rs b/src/ui/toolbar/apply/layout.rs index 96ae6563..2c5f0306 100644 --- a/src/ui/toolbar/apply/layout.rs +++ b/src/ui/toolbar/apply/layout.rs @@ -1,5 +1,6 @@ use crate::config::ToolbarLayoutMode; use crate::input::{InputState, ToolbarDrawerTab}; +use crate::ui::toolbar::ToolbarSideSection; impl InputState { pub(super) fn apply_toolbar_toggle_custom_section(&mut self, enable: bool) -> bool { @@ -269,6 +270,23 @@ impl InputState { changed } + pub(super) fn apply_toolbar_toggle_side_section_collapsed( + &mut self, + section: ToolbarSideSection, + collapsed: bool, + ) -> bool { + let changed = if collapsed { + self.toolbar_collapsed_side_sections.insert(section) + } else { + self.toolbar_collapsed_side_sections.remove(§ion) + }; + if !changed { + return false; + } + self.needs_redraw = true; + true + } + pub(super) fn apply_toolbar_set_layout_mode(&mut self, mode: ToolbarLayoutMode) -> bool { if self.toolbar_layout_mode != mode { self.toolbar_layout_mode = mode; diff --git a/src/ui/toolbar/apply/mod.rs b/src/ui/toolbar/apply/mod.rs index 25aca215..b7378318 100644 --- a/src/ui/toolbar/apply/mod.rs +++ b/src/ui/toolbar/apply/mod.rs @@ -131,6 +131,9 @@ impl InputState { } ToolbarEvent::ToggleDrawer(open) => self.apply_toolbar_toggle_drawer(open), ToolbarEvent::SetDrawerTab(tab) => self.apply_toolbar_set_drawer_tab(tab), + ToolbarEvent::ToggleSideSectionCollapsed(section, collapsed) => { + self.apply_toolbar_toggle_side_section_collapsed(section, collapsed) + } ToolbarEvent::SetToolbarLayoutMode(mode) => self.apply_toolbar_set_layout_mode(mode), ToolbarEvent::ToggleShapePicker(open) => self.apply_toolbar_toggle_shape_picker(open), ToolbarEvent::ApplyPreset(slot) => self.apply_toolbar_apply_preset(slot), diff --git a/src/ui/toolbar/events.rs b/src/ui/toolbar/events.rs index 8786cd1c..c634dd25 100644 --- a/src/ui/toolbar/events.rs +++ b/src/ui/toolbar/events.rs @@ -6,6 +6,49 @@ use crate::input::{EraserMode, Tool, ToolbarDrawerTab}; use super::ToolbarSnapshot; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ToolbarSideSection { + Colors, + Presets, + Thickness, + EraserMode, + PolygonSides, + ArrowLabels, + StepMarkers, + MarkerOpacity, + TextSize, + Font, + Actions, + Boards, + Pages, + StepUndo, + Session, + Settings, +} + +impl ToolbarSideSection { + pub fn label(self) -> &'static str { + match self { + Self::Colors => "Colors", + Self::Presets => "Presets", + Self::Thickness => "Thickness", + Self::EraserMode => "Eraser mode", + Self::PolygonSides => "Sides", + Self::ArrowLabels => "Arrow labels", + Self::StepMarkers => "Step markers", + Self::MarkerOpacity => "Marker opacity", + Self::TextSize => "Text size", + Self::Font => "Font", + Self::Actions => "Actions", + Self::Boards => "Boards", + Self::Pages => "Pages", + Self::StepUndo => "Step Undo/Redo", + Self::Session => "Session", + Self::Settings => "Settings", + } + } +} + /// Events emitted by the floating toolbar UI. #[derive(Debug, Clone)] pub enum ToolbarEvent { @@ -133,6 +176,8 @@ pub enum ToolbarEvent { ToggleDrawer(bool), /// Switch the active drawer tab SetDrawerTab(ToolbarDrawerTab), + /// Collapse/expand a section in the side drawer + ToggleSideSectionCollapsed(ToolbarSideSection, bool), /// Set toolbar layout mode SetToolbarLayoutMode(ToolbarLayoutMode), /// Toggle the simple-mode shape picker diff --git a/src/ui/toolbar/mod.rs b/src/ui/toolbar/mod.rs index 6c28c157..2f1a8715 100644 --- a/src/ui/toolbar/mod.rs +++ b/src/ui/toolbar/mod.rs @@ -6,7 +6,7 @@ pub(crate) mod model; pub mod snapshot; pub use bindings::ToolbarBindingHints; -pub use events::ToolbarEvent; +pub use events::{ToolbarEvent, ToolbarSideSection}; #[allow(unused_imports)] pub use snapshot::{ PresetFeedbackSnapshot, PresetSlotSnapshot, SessionRecentSnapshot, ToolContext, diff --git a/src/ui/toolbar/snapshot/build.rs b/src/ui/toolbar/snapshot/build.rs index 5772a366..325be6c4 100644 --- a/src/ui/toolbar/snapshot/build.rs +++ b/src/ui/toolbar/snapshot/build.rs @@ -171,6 +171,7 @@ impl ToolbarSnapshot { show_text_controls: state.show_text_controls, context_aware_ui: state.context_aware_ui, show_settings_section, + collapsed_side_sections: state.toolbar_collapsed_side_sections.clone(), show_tool_preview: state.show_tool_preview, show_status_bar: state.show_status_bar, show_status_board_badge: state.show_status_board_badge, diff --git a/src/ui/toolbar/snapshot/types.rs b/src/ui/toolbar/snapshot/types.rs index 3c9ef1ad..1cf53780 100644 --- a/src/ui/toolbar/snapshot/types.rs +++ b/src/ui/toolbar/snapshot/types.rs @@ -6,6 +6,8 @@ use crate::input::{EraserMode, Tool, ToolbarDrawerTab}; use std::path::PathBuf; use super::super::bindings::ToolbarBindingHints; +use super::super::events::ToolbarSideSection; +use std::collections::BTreeSet; /// The kind of tool-specific options to display in the side panel. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -295,6 +297,8 @@ pub struct ToolbarSnapshot { pub context_aware_ui: bool, /// Whether to show the Settings section pub show_settings_section: bool, + /// Side drawer sections whose body content is collapsed. + pub collapsed_side_sections: BTreeSet, pub show_tool_preview: bool, pub show_status_bar: bool, pub show_status_board_badge: bool, @@ -331,3 +335,9 @@ pub struct ToolbarSnapshot { /// Save Session As target waiting for explicit overwrite confirmation. pub pending_save_as_overwrite_path: Option, } + +impl ToolbarSnapshot { + pub fn side_section_collapsed(&self, section: ToolbarSideSection) -> bool { + self.collapsed_side_sections.contains(§ion) + } +}