fix(ribbon): highlight follows each tool's mechanism; start-tab dimming (#355)
Tool highlights used to stick: every click set active_tool, but only interactive commands and a hand-maintained per-modal list ever cleared it, so one-shots (Cleanup/AUDIT, view actions) and unlisted dialogs (Customization/ALIASEDIT, CUI, Plugin Manager…) stayed blue forever. Now the rule is mechanical: state toggles light from state only (the click highlight drops immediately), interactive commands stay lit while running, dialog openers stay lit until the modal closes (generic clear replaces the per-modal list), and one-shots never keep it — the dispatch site turns the highlight off when nothing stayed running. On the Start tab, tools whose command the start gate refuses now render dimmed (grey icon + label) across all button styles, dropdowns and the quick-access strip; start_allowed() is the single shared authority. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7a93ba82b3
commit
b7457ba09d
7 changed files with 159 additions and 90 deletions
|
|
@ -108,27 +108,7 @@ impl OpenCADStudio {
|
||||||
// editors (shortcuts, aliases) — none of them read the scene. This is
|
// editors (shortcuts, aliases) — none of them read the scene. This is
|
||||||
// the single place that decides; `on_ribbon_tool_click` defers to it
|
// the single place that decides; `on_ribbon_tool_click` defers to it
|
||||||
// rather than keeping a second, blunter copy (#388, #389).
|
// rather than keeping a second, blunter copy (#388, #389).
|
||||||
if self.tabs[i].is_start
|
if self.tabs[i].is_start && !start_allowed(cmd) {
|
||||||
&& !matches!(
|
|
||||||
cmd,
|
|
||||||
"NEW"
|
|
||||||
| "OPEN"
|
|
||||||
| "EXIT"
|
|
||||||
| "QUIT"
|
|
||||||
| "REPORT"
|
|
||||||
| "CHANGELOG"
|
|
||||||
| "ABOUT"
|
|
||||||
| "PLUGINS"
|
|
||||||
| "PLUGINMANAGER"
|
|
||||||
| "DONATE"
|
|
||||||
| "WEBVERSION"
|
|
||||||
| "HELP"
|
|
||||||
| "CUI"
|
|
||||||
| "ALIASEDIT"
|
|
||||||
| "CUILOAD"
|
|
||||||
| "CUIIMPORT"
|
|
||||||
)
|
|
||||||
{
|
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_info("No drawing open. Use NEW or OPEN to start a drawing.");
|
.push_info("No drawing open. Use NEW or OPEN to start a drawing.");
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|
@ -235,6 +215,32 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `cmd` makes sense on the Start (welcome) tab — document lifecycle,
|
||||||
|
/// links, and app-wide configuration; nothing that reads the scene. Single
|
||||||
|
/// source of truth: the dispatch gate refuses everything else, and the ribbon
|
||||||
|
/// dims the tools this rejects.
|
||||||
|
pub fn start_allowed(cmd: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
cmd,
|
||||||
|
"NEW"
|
||||||
|
| "OPEN"
|
||||||
|
| "EXIT"
|
||||||
|
| "QUIT"
|
||||||
|
| "REPORT"
|
||||||
|
| "CHANGELOG"
|
||||||
|
| "ABOUT"
|
||||||
|
| "PLUGINS"
|
||||||
|
| "PLUGINMANAGER"
|
||||||
|
| "DONATE"
|
||||||
|
| "WEBVERSION"
|
||||||
|
| "HELP"
|
||||||
|
| "CUI"
|
||||||
|
| "ALIASEDIT"
|
||||||
|
| "CUILOAD"
|
||||||
|
| "CUIIMPORT"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Autocomplete registry — one-shot commands ──────────────────────────────
|
// ── Autocomplete registry — one-shot commands ──────────────────────────────
|
||||||
// These commands dispatch a single action (file ops, view, layer/style
|
// These commands dispatch a single action (file ops, view, layer/style
|
||||||
// managers, undo/redo, …) rather than installing an interactive `CadCommand`,
|
// managers, undo/redo, …) rather than installing an interactive `CadCommand`,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ mod config;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use automation::{export_headless, serve};
|
pub use automation::{export_headless, serve};
|
||||||
mod command_driver;
|
mod command_driver;
|
||||||
mod commands;
|
pub(crate) mod commands;
|
||||||
mod document;
|
mod document;
|
||||||
mod expr_eval;
|
mod expr_eval;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,20 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||||
self.ribbon.note_panel_tool(&tool_id);
|
self.ribbon.note_panel_tool(&tool_id);
|
||||||
self.ribbon.activate_tool(&tool_id);
|
self.ribbon.activate_tool(&tool_id);
|
||||||
match event {
|
match event {
|
||||||
ModuleEvent::Command(cmd) => return self.dispatch_command(&cmd),
|
ModuleEvent::Command(cmd) => {
|
||||||
|
let task = self.dispatch_command(&cmd);
|
||||||
|
// One-shot tools (view changes, clipboard, toggles,
|
||||||
|
// audits…) leave nothing running: no interactive
|
||||||
|
// command and no dialog. Their highlight would stick
|
||||||
|
// forever — turn it off now. Interactive commands and
|
||||||
|
// dialog owners keep theirs; the command end / modal
|
||||||
|
// close clears those. (#355)
|
||||||
|
let i = self.active_tab;
|
||||||
|
if self.tabs[i].active_cmd.is_none() && self.active_modal.is_none() {
|
||||||
|
self.ribbon.deactivate_tool();
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
ModuleEvent::OpenFileDialog => {
|
ModuleEvent::OpenFileDialog => {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_info("Open DWG/DXF: not yet implemented.");
|
.push_info("Open DWG/DXF: not yet implemented.");
|
||||||
|
|
@ -166,6 +179,10 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Every non-Command event above is a one-shot (state toggle,
|
||||||
|
// clear, dialog spawn) — nothing stays running to clear the
|
||||||
|
// highlight later, so turn it off here. (#355)
|
||||||
|
self.ribbon.deactivate_tool();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,33 +81,6 @@ impl OpenCADStudio {
|
||||||
self.scale_stage_discard();
|
self.scale_stage_discard();
|
||||||
}
|
}
|
||||||
match self.active_modal {
|
match self.active_modal {
|
||||||
Some(Layers) => self.ribbon.deactivate_tool_if("LAYERS"),
|
|
||||||
Some(Plot) => {
|
|
||||||
self.ribbon.deactivate_tool_if("PLOT");
|
|
||||||
self.ribbon.deactivate_tool_if("PRINT");
|
|
||||||
self.ribbon.deactivate_tool_if("PAGESETUP");
|
|
||||||
}
|
|
||||||
Some(TextStyle) => {
|
|
||||||
self.ribbon.deactivate_tool_if("STYLE");
|
|
||||||
self.ribbon.deactivate_tool_if("TEXTSTYLE");
|
|
||||||
}
|
|
||||||
Some(TableStyle) => self.ribbon.deactivate_tool_if("TABLESTYLE"),
|
|
||||||
Some(MlStyle) => self.ribbon.deactivate_tool_if("MLSTYLE"),
|
|
||||||
Some(MLeaderStyle) => self.ribbon.deactivate_tool_if("MLEADERSTYLE"),
|
|
||||||
Some(LayoutManager) => {
|
|
||||||
self.ribbon.deactivate_tool_if("LAYOUTMANAGER");
|
|
||||||
self.ribbon.deactivate_tool_if("LAYOUTPANEL");
|
|
||||||
}
|
|
||||||
Some(Plotstyle) => {
|
|
||||||
self.ribbon.deactivate_tool_if("PLOTSTYLE");
|
|
||||||
self.ribbon.deactivate_tool_if("STYLESMANAGER");
|
|
||||||
}
|
|
||||||
Some(DimStyle) => self.ribbon.deactivate_tool_if("DIMSTYLE"),
|
|
||||||
Some(Shortcuts) => {
|
|
||||||
self.ribbon.deactivate_tool_if("SHORTCUTS");
|
|
||||||
self.ribbon.deactivate_tool_if("KEYBOARD");
|
|
||||||
}
|
|
||||||
Some(About) => self.ribbon.deactivate_tool_if("ABOUT"),
|
|
||||||
// Dismissing these via ✕ is the cancel/decline path.
|
// Dismissing these via ✕ is the cancel/decline path.
|
||||||
Some(Unsaved) => self.pending_close = None,
|
Some(Unsaved) => self.pending_close = None,
|
||||||
Some(AssocPrompt) => self.mark_assoc_prompted(),
|
Some(AssocPrompt) => self.mark_assoc_prompted(),
|
||||||
|
|
@ -125,10 +98,17 @@ impl OpenCADStudio {
|
||||||
// style editors. Committing happens only through the Apply button.
|
// style editors. Committing happens only through the Apply button.
|
||||||
Some(Aliases) => {
|
Some(Aliases) => {
|
||||||
self.alias_editor_rows.clear();
|
self.alias_editor_rows.clear();
|
||||||
self.ribbon.deactivate_tool_if("ALIASEDIT");
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
// The tool that opened this dialog is done with it now. Keep the
|
||||||
|
// highlight only while an interactive command still runs (it owns
|
||||||
|
// it). Replaces the old per-modal deactivate_tool_if list, which
|
||||||
|
// missed every newly added dialog (CUI, Plugin Manager, Point
|
||||||
|
// Style, Attribute Editor…). (#355)
|
||||||
|
if self.tabs[self.active_tab].active_cmd.is_none() {
|
||||||
|
self.ribbon.deactivate_tool();
|
||||||
|
}
|
||||||
self.active_modal = None;
|
self.active_modal = None;
|
||||||
// Recentre / reset the size of the next dialog and drop any drag.
|
// Recentre / reset the size of the next dialog and drop any drag.
|
||||||
self.modal_offset = iced::Vector::ZERO;
|
self.modal_offset = iced::Vector::ZERO;
|
||||||
|
|
|
||||||
|
|
@ -1304,6 +1304,7 @@ impl OpenCADStudio {
|
||||||
if !self.clean_screen {
|
if !self.clean_screen {
|
||||||
col = col.push(self.ribbon.view(
|
col = col.push(self.ribbon.view(
|
||||||
is_paper,
|
is_paper,
|
||||||
|
self.tabs[self.active_tab].is_start,
|
||||||
self.tabs[self.active_tab].history.undo_stack.len(),
|
self.tabs[self.active_tab].history.undo_stack.len(),
|
||||||
self.tabs[self.active_tab].history.redo_stack.len(),
|
self.tabs[self.active_tab].history.redo_stack.len(),
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -312,6 +312,7 @@ impl Ribbon {
|
||||||
fn toggle_state(&self) -> widgets::ToggleState {
|
fn toggle_state(&self) -> widgets::ToggleState {
|
||||||
use widgets::ToggleState;
|
use widgets::ToggleState;
|
||||||
ToggleState {
|
ToggleState {
|
||||||
|
start_mode: false,
|
||||||
wireframe: self.wireframe,
|
wireframe: self.wireframe,
|
||||||
ortho_mode: self.ortho_mode,
|
ortho_mode: self.ortho_mode,
|
||||||
show_viewcube: self.show_viewcube,
|
show_viewcube: self.show_viewcube,
|
||||||
|
|
@ -387,16 +388,17 @@ impl Ribbon {
|
||||||
pub fn view(
|
pub fn view(
|
||||||
&self,
|
&self,
|
||||||
is_paper: bool,
|
is_paper: bool,
|
||||||
|
is_start: bool,
|
||||||
undo_count: usize,
|
undo_count: usize,
|
||||||
redo_count: usize,
|
redo_count: usize,
|
||||||
) -> Element<'_, Message> {
|
) -> Element<'_, Message> {
|
||||||
// ── Quick-access file commands + undo/redo, one merged flow ────────
|
// ── Quick-access file commands + undo/redo, one merged flow ────────
|
||||||
let lead = WrapFlow::new(vec![
|
let lead = WrapFlow::new(vec![
|
||||||
quick_access_btn(crate::ui::icons::DOC_NEW, "New", "NEW").into(),
|
quick_access_btn(crate::ui::icons::DOC_NEW, "New", "NEW", is_start).into(),
|
||||||
quick_access_btn(crate::ui::icons::FOLDER_OPEN, "Open", "OPEN").into(),
|
quick_access_btn(crate::ui::icons::FOLDER_OPEN, "Open", "OPEN", is_start).into(),
|
||||||
quick_access_btn(crate::ui::icons::SAVE, "Save", "SAVE").into(),
|
quick_access_btn(crate::ui::icons::SAVE, "Save", "SAVE", is_start).into(),
|
||||||
quick_access_btn(crate::ui::icons::FILE_EXPORT, "Save As", "SAVEAS").into(),
|
quick_access_btn(crate::ui::icons::FILE_EXPORT, "Save As", "SAVEAS", is_start).into(),
|
||||||
quick_access_btn(crate::ui::icons::PRINT, "Print", "PRINT").into(),
|
quick_access_btn(crate::ui::icons::PRINT, "Print", "PRINT", is_start).into(),
|
||||||
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown).into(),
|
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown).into(),
|
||||||
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown).into(),
|
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown).into(),
|
||||||
])
|
])
|
||||||
|
|
@ -575,7 +577,8 @@ impl Ribbon {
|
||||||
let panels: Vec<Panel<'_>> = groups
|
let panels: Vec<Panel<'_>> = groups
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| {
|
.map(|g| {
|
||||||
let ts = self.toggle_state();
|
let mut ts = self.toggle_state();
|
||||||
|
ts.start_mode = is_start;
|
||||||
Panel {
|
Panel {
|
||||||
id: g.title.to_string(),
|
id: g.title.to_string(),
|
||||||
full: render_group(
|
full: render_group(
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ use super::LayerInfo;
|
||||||
/// and call site.
|
/// and call site.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(super) struct ToggleState {
|
pub(super) struct ToggleState {
|
||||||
|
/// Start (welcome) tab is active — tools whose command the start-tab
|
||||||
|
/// gate refuses render dimmed and read as unusable.
|
||||||
|
pub start_mode: bool,
|
||||||
pub wireframe: bool,
|
pub wireframe: bool,
|
||||||
pub ortho_mode: bool,
|
pub ortho_mode: bool,
|
||||||
pub show_viewcube: bool,
|
pub show_viewcube: bool,
|
||||||
|
|
@ -299,6 +302,32 @@ pub(super) fn make_icon(icon: IconKind, size: f32) -> Element<'static, Message>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unusable on the Start tab: dim it. Mirrors the dispatch gate — the single
|
||||||
|
/// authority is `crate::app::commands::start_allowed`.
|
||||||
|
pub(super) fn start_dimmed(state: &ToggleState, event: &ModuleEvent) -> bool {
|
||||||
|
state.start_mode
|
||||||
|
&& !matches!(event, ModuleEvent::Command(c) if crate::app::commands::start_allowed(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Label / glyph color for a possibly-dimmed tool.
|
||||||
|
pub(super) const DIM_TOOL: Color = Color {
|
||||||
|
r: 0.42,
|
||||||
|
g: 0.42,
|
||||||
|
b: 0.45,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `make_icon`, greyed out when `dim` (SVGs render monochrome via tint).
|
||||||
|
pub(super) fn make_icon_dim(icon: IconKind, size: f32, dim: bool) -> Element<'static, Message> {
|
||||||
|
if !dim {
|
||||||
|
return make_icon(icon, size);
|
||||||
|
}
|
||||||
|
match icon {
|
||||||
|
IconKind::Glyph(s) => text(s).size(size * 0.7).color(DIM_TOOL).into(),
|
||||||
|
IconKind::Svg(bytes) => icons::tinted(bytes, size, DIM_TOOL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn is_active_tool(
|
pub(super) fn is_active_tool(
|
||||||
id: &str,
|
id: &str,
|
||||||
active_tool: &Option<String>,
|
active_tool: &Option<String>,
|
||||||
|
|
@ -373,10 +402,11 @@ pub(super) fn render_small<'a>(
|
||||||
// large buttons to icon-only columns when the width is tight.
|
// large buttons to icon-only columns when the width is tight.
|
||||||
RibbonItem::Tool(t) | RibbonItem::LargeTool(t) => {
|
RibbonItem::Tool(t) | RibbonItem::LargeTool(t) => {
|
||||||
let active = is_active_tool(t.id, active_tool, &state);
|
let active = is_active_tool(t.id, active_tool, &state);
|
||||||
|
let dim = start_dimmed(&state, &t.event);
|
||||||
let event = t.event.clone();
|
let event = t.event.clone();
|
||||||
let tool_id = t.id.to_string();
|
let tool_id = t.id.to_string();
|
||||||
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
||||||
let btn = button(make_icon(t.icon, SMALL_ICON))
|
let btn = button(make_icon_dim(t.icon, SMALL_ICON, dim))
|
||||||
.on_press(Message::RibbonToolClick { tool_id, event })
|
.on_press(Message::RibbonToolClick { tool_id, event })
|
||||||
.style(move |_: &Theme, status| tool_btn_style(active, status))
|
.style(move |_: &Theme, status| tool_btn_style(active, status))
|
||||||
.width(Length::Fixed(SMALL_W))
|
.width(Length::Fixed(SMALL_W))
|
||||||
|
|
@ -403,10 +433,15 @@ pub(super) fn render_small<'a>(
|
||||||
default,
|
default,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let active = active_tool.as_deref() == Some(*id)
|
let dim = state.start_mode
|
||||||
|| items
|
&& !items
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(cmd, _, _)| active_tool.as_deref() == Some(*cmd));
|
.any(|(cmd, _, _)| crate::app::commands::start_allowed(cmd));
|
||||||
|
let active = !dim
|
||||||
|
&& (active_tool.as_deref() == Some(*id)
|
||||||
|
|| items
|
||||||
|
.iter()
|
||||||
|
.any(|(cmd, _, _)| active_tool.as_deref() == Some(*cmd)));
|
||||||
let dd_open = open_dd.as_deref() == Some(*id);
|
let dd_open = open_dd.as_deref() == Some(*id);
|
||||||
let last = last_cmd.get(id).copied().unwrap_or(*default);
|
let last = last_cmd.get(id).copied().unwrap_or(*default);
|
||||||
let cur_icon = last_cmd
|
let cur_icon = last_cmd
|
||||||
|
|
@ -434,7 +469,7 @@ pub(super) fn render_small<'a>(
|
||||||
.unwrap_or(*id);
|
.unwrap_or(*id);
|
||||||
let tip_text = format!("{}\nCommand: {}", cur_label, last);
|
let tip_text = format!("{}\nCommand: {}", cur_label, last);
|
||||||
|
|
||||||
let icon_btn = button(make_icon(cur_icon, SMALL_ICON))
|
let icon_btn = button(make_icon_dim(cur_icon, SMALL_ICON, dim))
|
||||||
.on_press(Message::RibbonToolClick {
|
.on_press(Message::RibbonToolClick {
|
||||||
tool_id: last.to_string(),
|
tool_id: last.to_string(),
|
||||||
event: ModuleEvent::Command(last.to_string()),
|
event: ModuleEvent::Command(last.to_string()),
|
||||||
|
|
@ -505,11 +540,13 @@ pub(super) fn render_large_dropdown<'a>(
|
||||||
active_tool: &Option<String>,
|
active_tool: &Option<String>,
|
||||||
open_dd: &Option<String>,
|
open_dd: &Option<String>,
|
||||||
last_cmd: &HashMap<&'static str, &'static str>,
|
last_cmd: &HashMap<&'static str, &'static str>,
|
||||||
|
dim: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let active = active_tool.as_deref() == Some(id)
|
let active = !dim
|
||||||
|| items
|
&& (active_tool.as_deref() == Some(id)
|
||||||
.iter()
|
|| items
|
||||||
.any(|(cmd, _, _)| active_tool.as_deref() == Some(*cmd));
|
.iter()
|
||||||
|
.any(|(cmd, _, _)| active_tool.as_deref() == Some(*cmd)));
|
||||||
let dd_open = open_dd.as_deref() == Some(id);
|
let dd_open = open_dd.as_deref() == Some(id);
|
||||||
let last = last_cmd.get(id).copied().unwrap_or(default);
|
let last = last_cmd.get(id).copied().unwrap_or(default);
|
||||||
let cur_icon = last_cmd
|
let cur_icon = last_cmd
|
||||||
|
|
@ -531,8 +568,8 @@ pub(super) fn render_large_dropdown<'a>(
|
||||||
// Icon on top with the label beneath it, then the ▾ strip at the very bottom.
|
// Icon on top with the label beneath it, then the ▾ strip at the very bottom.
|
||||||
let top_btn = button(
|
let top_btn = button(
|
||||||
column![
|
column![
|
||||||
make_icon(cur_icon, LARGE_ICON),
|
make_icon_dim(cur_icon, LARGE_ICON, dim),
|
||||||
text(label.to_string()).size(10).color(LABEL_COLOR),
|
text(label.to_string()).size(10).color(if dim { DIM_TOOL } else { LABEL_COLOR }),
|
||||||
]
|
]
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
|
|
@ -615,13 +652,14 @@ pub(super) fn render_large<'a>(
|
||||||
// representative tool as a big icon.
|
// representative tool as a big icon.
|
||||||
RibbonItem::LargeTool(t) | RibbonItem::Tool(t) => {
|
RibbonItem::LargeTool(t) | RibbonItem::Tool(t) => {
|
||||||
let active = is_active_tool(t.id, active_tool, &state);
|
let active = is_active_tool(t.id, active_tool, &state);
|
||||||
|
let dim = start_dimmed(&state, &t.event);
|
||||||
let event = t.event.clone();
|
let event = t.event.clone();
|
||||||
let tool_id = t.id.to_string();
|
let tool_id = t.id.to_string();
|
||||||
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
||||||
let btn = button(
|
let btn = button(
|
||||||
column![
|
column![
|
||||||
make_icon(t.icon, LARGE_ICON),
|
make_icon_dim(t.icon, LARGE_ICON, dim),
|
||||||
text(t.label).size(10).color(LABEL_COLOR),
|
text(t.label).size(10).color(if dim { DIM_TOOL } else { LABEL_COLOR }),
|
||||||
]
|
]
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
|
|
@ -649,16 +687,23 @@ pub(super) fn render_large<'a>(
|
||||||
icon,
|
icon,
|
||||||
items,
|
items,
|
||||||
default,
|
default,
|
||||||
} => render_large_dropdown(
|
} => {
|
||||||
*id,
|
let dim = state.start_mode
|
||||||
*icon,
|
&& !items
|
||||||
Some(*label),
|
.iter()
|
||||||
items,
|
.any(|(cmd, _, _)| crate::app::commands::start_allowed(cmd));
|
||||||
*default,
|
render_large_dropdown(
|
||||||
active_tool,
|
*id,
|
||||||
open_dd,
|
*icon,
|
||||||
last_cmd,
|
Some(*label),
|
||||||
),
|
items,
|
||||||
|
*default,
|
||||||
|
active_tool,
|
||||||
|
open_dd,
|
||||||
|
last_cmd,
|
||||||
|
dim,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// A plain Dropdown renders large too (used by a collapsed panel whose
|
// A plain Dropdown renders large too (used by a collapsed panel whose
|
||||||
// representative tool is a dropdown).
|
// representative tool is a dropdown).
|
||||||
|
|
@ -667,9 +712,16 @@ pub(super) fn render_large<'a>(
|
||||||
icon,
|
icon,
|
||||||
items,
|
items,
|
||||||
default,
|
default,
|
||||||
} => render_large_dropdown(
|
} => {
|
||||||
*id, *icon, None, items, *default, active_tool, open_dd, last_cmd,
|
let dim = state.start_mode
|
||||||
),
|
&& !items
|
||||||
|
.iter()
|
||||||
|
.any(|(cmd, _, _)| crate::app::commands::start_allowed(cmd));
|
||||||
|
render_large_dropdown(
|
||||||
|
*id, *icon, None, items, *default, active_tool, open_dd, last_cmd,
|
||||||
|
dim,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
RibbonItem::LayerComboGroup { row2, row3 } => {
|
RibbonItem::LayerComboGroup { row2, row3 } => {
|
||||||
const COMBO_W: f32 = LARGE_W * 2.5;
|
const COMBO_W: f32 = LARGE_W * 2.5;
|
||||||
|
|
@ -736,9 +788,12 @@ pub(super) fn render_large<'a>(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let is_active = active_tool.as_deref() == Some(t.id);
|
let is_active = active_tool.as_deref() == Some(t.id);
|
||||||
|
let dim = start_dimmed(&state, &t.event);
|
||||||
let tip = t.label;
|
let tip = t.label;
|
||||||
let event = t.event.clone();
|
let event = t.event.clone();
|
||||||
let icon_el: Element<Message> = match t.icon {
|
let icon_el: Element<Message> = if dim {
|
||||||
|
make_icon_dim(t.icon, 16.0, true)
|
||||||
|
} else { match t.icon {
|
||||||
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
||||||
IconKind::Svg(bytes) => {
|
IconKind::Svg(bytes) => {
|
||||||
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
||||||
|
|
@ -746,7 +801,7 @@ pub(super) fn render_large<'a>(
|
||||||
.height(16)
|
.height(16)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
};
|
} };
|
||||||
let msg = module_event_to_message(event);
|
let msg = module_event_to_message(event);
|
||||||
tooltip(
|
tooltip(
|
||||||
button(icon_el)
|
button(icon_el)
|
||||||
|
|
@ -802,13 +857,14 @@ pub(super) fn render_large<'a>(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let mp_active = is_active_tool(match_prop.id, active_tool, &state);
|
let mp_active = is_active_tool(match_prop.id, active_tool, &state);
|
||||||
|
let mp_dim = start_dimmed(&state, &match_prop.event);
|
||||||
let mp_event = match_prop.event.clone();
|
let mp_event = match_prop.event.clone();
|
||||||
let mp_id = match_prop.id.to_string();
|
let mp_id = match_prop.id.to_string();
|
||||||
let mp_tip = format!("{}\nCommand: {}", match_prop.label, match_prop.id);
|
let mp_tip = format!("{}\nCommand: {}", match_prop.label, match_prop.id);
|
||||||
let mp_btn = button(
|
let mp_btn = button(
|
||||||
column![
|
column![
|
||||||
make_icon(match_prop.icon, LARGE_ICON),
|
make_icon_dim(match_prop.icon, LARGE_ICON, mp_dim),
|
||||||
text(match_prop.label).size(10).color(LABEL_COLOR),
|
text(match_prop.label).size(10).color(if mp_dim { DIM_TOOL } else { LABEL_COLOR }),
|
||||||
]
|
]
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
|
|
@ -984,9 +1040,12 @@ pub(super) fn render_large<'a>(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let is_active = active_tool.as_deref() == Some(t.id);
|
let is_active = active_tool.as_deref() == Some(t.id);
|
||||||
|
let dim = start_dimmed(&state, &t.event);
|
||||||
let tip = t.label;
|
let tip = t.label;
|
||||||
let event = t.event.clone();
|
let event = t.event.clone();
|
||||||
let icon_el: Element<Message> = match t.icon {
|
let icon_el: Element<Message> = if dim {
|
||||||
|
make_icon_dim(t.icon, 16.0, true)
|
||||||
|
} else { match t.icon {
|
||||||
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
||||||
IconKind::Svg(bytes) => {
|
IconKind::Svg(bytes) => {
|
||||||
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
||||||
|
|
@ -994,7 +1053,7 @@ pub(super) fn render_large<'a>(
|
||||||
.height(16)
|
.height(16)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
};
|
} };
|
||||||
let msg = module_event_to_message(event);
|
let msg = module_event_to_message(event);
|
||||||
tooltip(
|
tooltip(
|
||||||
button(icon_el)
|
button(icon_el)
|
||||||
|
|
@ -1063,10 +1122,13 @@ pub(super) fn quick_access_btn<'a>(
|
||||||
icon_bytes: &'static [u8],
|
icon_bytes: &'static [u8],
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
cmd: &'static str,
|
cmd: &'static str,
|
||||||
|
is_start: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
// The bundled UI SVGs are black-stroked; tint them to a light chrome grey so
|
// The bundled UI SVGs are black-stroked; tint them to a light chrome grey so
|
||||||
// they read on the dark top strip (raw black is invisible there).
|
// they read on the dark top strip (raw black is invisible there).
|
||||||
let icon = icons::tinted(icon_bytes, 16.0, QA_ICON_COLOR);
|
// On the Start tab, commands the start gate refuses render dimmed.
|
||||||
|
let dim = is_start && !crate::app::commands::start_allowed(cmd);
|
||||||
|
let icon = icons::tinted(icon_bytes, 16.0, if dim { DIM_TOOL } else { QA_ICON_COLOR });
|
||||||
let btn = button(
|
let btn = button(
|
||||||
container(icon)
|
container(icon)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue