mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-14 14:26:45 -04:00
Tool Sets: the gear on a category header opens that category's options (#620)
Squash of PR #620 by @switch87. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9b3674e01f
commit
4e276db858
5 files changed
+126
-2
No files matched your search
@@ -22,6 +22,11 @@ real_name = "Brandon Thomas"
|
||||
display_name = "Brandon Thomas"
|
||||
sync_github_name = false
|
||||
|
||||
[people.switch87]
|
||||
real_name = "Gert Pellin"
|
||||
display_name = "Gert Pellin"
|
||||
sync_github_name = false
|
||||
|
||||
[people.rangoDJ]
|
||||
sync_github_name = true
|
||||
|
||||
|
||||
@@ -840,6 +840,8 @@
|
||||
Show All Показати всі
|
||||
Show All Constraint Bars Показати всі панелі обмежень
|
||||
Show All Dynamic Dimensions Показати всі динамічні розміри
|
||||
Show and hide the tools of this category Показати й сховати інструменти цієї категорії
|
||||
Show in Tool Sets Показувати в наборах інструментів
|
||||
Show command prompting near the crosshairs Показувати запити команд біля перехрестя
|
||||
Show Layer List Показати список шарів
|
||||
Show/Hide Lineweight Показати/приховати товщину ліній
|
||||
|
||||
|
@@ -77,6 +77,9 @@ pub struct UiState {
|
||||
/// "Drafting" or "Modeling".
|
||||
pub toolset_tab: String,
|
||||
pub collapsed_groups: Vec<String>,
|
||||
/// Tools the user took out of the Tool Sets palette (command ids); each category's gear
|
||||
/// menu puts them back.
|
||||
pub hidden_tools: Vec<String>,
|
||||
pub properties_all: bool,
|
||||
/// Show an in-window menu bar (platforms without a native menu bar, or on request).
|
||||
pub in_window_menu: bool,
|
||||
@@ -150,6 +153,7 @@ impl Default for UiState {
|
||||
system_cursor: std::env::var("CADCRAFT_SYSTEM_CURSOR").is_ok_and(|v| !matches!(v.trim(), "" | "0" | "false" | "off")),
|
||||
toolset_tab: "Drafting".into(),
|
||||
collapsed_groups: Vec::new(),
|
||||
hidden_tools: Vec::new(),
|
||||
properties_all: true,
|
||||
in_window_menu: !cfg!(target_os = "macos"),
|
||||
start_tab: false,
|
||||
|
||||
@@ -160,6 +160,8 @@ pub fn toolsets(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
let body = Rect::from_min_max(pos2(r.left(), r.top() + tab_h), r.max);
|
||||
let mut clicked: Option<&'static str> = None;
|
||||
let mut toggle_group = None;
|
||||
let mut tool_visible: Option<(String, bool)> = None;
|
||||
let mut restore: Vec<String> = Vec::new();
|
||||
ui.scope_builder(egui::UiBuilder::new().max_rect(body), |ui| {
|
||||
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(0.0, 0.0);
|
||||
@@ -182,11 +184,28 @@ pub fn toolsets(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
egui::FontId::proportional(12.5),
|
||||
t.text,
|
||||
);
|
||||
icons::paint(pp, Rect::from_center_size(pos2(hr.right() - 12.0, hr.center().y), vec2(11.0, 11.0)), Icon::Gear, false);
|
||||
let gear = Rect::from_center_size(pos2(hr.right() - 12.0, hr.center().y), vec2(13.0, 13.0));
|
||||
let gresp = ui.interact(gear, ui.id().with(("ts_gear", name)), Sense::click());
|
||||
icons::paint(pp, Rect::from_center_size(gear.center(), vec2(11.0, 11.0)), Icon::Gear, false);
|
||||
icons::describe_control(ui, &hresp, name, Some(!collapsed), false);
|
||||
icons::describe_control(ui, &gresp, &format!("{name} options"), None, false);
|
||||
let gresp = gresp.on_hover_text(crate::tl!("Show and hide the tools of this category"));
|
||||
if hresp.clicked() {
|
||||
toggle_group = Some(name.to_string());
|
||||
}
|
||||
let hidden = |cmd: &str| app.ui.hidden_tools.iter().any(|h| h == cmd);
|
||||
egui::Popup::from_toggle_button_response(&gresp).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
|
||||
ui.label(crate::tl!("Show in Tool Sets"));
|
||||
for (_, cmd, tip) in large.iter().chain(small.iter()) {
|
||||
let mut shown = !hidden(cmd);
|
||||
if ui.checkbox(&mut shown, *tip).changed() {
|
||||
tool_visible = Some((cmd.to_string(), shown));
|
||||
}
|
||||
}
|
||||
if ui.selectable_label(false, crate::tl!("Show All")).clicked() {
|
||||
restore = large.iter().chain(small.iter()).map(|(_, cmd, _)| cmd.to_string()).collect();
|
||||
}
|
||||
});
|
||||
if !collapsed {
|
||||
ui.add_space(4.0);
|
||||
// Large icons.
|
||||
@@ -195,6 +214,9 @@ pub fn toolsets(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
ui.add_space(8.0);
|
||||
ui.spacing_mut().item_spacing = vec2(8.0, 0.0);
|
||||
for (icon, cmd, tip) in large.iter() {
|
||||
if hidden(cmd) {
|
||||
continue;
|
||||
}
|
||||
ui.add_enabled_ui(command_known(cmd), |ui| {
|
||||
if icons::button(ui, *icon, 40.0, tip, false).clicked() {
|
||||
clicked = Some(cmd);
|
||||
@@ -204,7 +226,8 @@ pub fn toolsets(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
});
|
||||
}
|
||||
// Small icons, 8 per row.
|
||||
for row in small.chunks(8) {
|
||||
let visible: Vec<Tool> = small.iter().copied().filter(|(_, cmd, _)| !hidden(cmd)).collect();
|
||||
for row in visible.chunks(8) {
|
||||
ui.add_space(2.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(8.0);
|
||||
@@ -225,6 +248,16 @@ pub fn toolsets(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
});
|
||||
});
|
||||
if let Some((cmd, shown)) = tool_visible {
|
||||
if shown {
|
||||
app.ui.hidden_tools.retain(|h| h != &cmd);
|
||||
} else if !app.ui.hidden_tools.contains(&cmd) {
|
||||
app.ui.hidden_tools.push(cmd);
|
||||
}
|
||||
}
|
||||
if !restore.is_empty() {
|
||||
app.ui.hidden_tools.retain(|h| !restore.contains(h));
|
||||
}
|
||||
if let Some(g) = toggle_group {
|
||||
if let Some(i) = app.ui.collapsed_groups.iter().position(|x| *x == g) {
|
||||
app.ui.collapsed_groups.remove(i);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//! The gear on a Tool Sets category header opens that category's options menu and no longer
|
||||
//! collapses the category it sits on.
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_ui_egui::{CadApp, Services};
|
||||
use egui::vec2;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
||||
/// A tool name that appears exactly once in the Drafting palette, so the accessibility tree can
|
||||
/// be queried for it without running into a toolbar button of the same name.
|
||||
const HIDDEN: &str = "Revision Cloud";
|
||||
|
||||
/// The same tool as a command id, which is what the UI state stores.
|
||||
const HIDDEN_CMD: &str = "revcloud";
|
||||
|
||||
fn harness() -> Harness<'static, CadApp> {
|
||||
let mut app = CadApp::new(Session::default(), Services::default());
|
||||
app.ui.in_window_menu = false;
|
||||
app.integrated_titlebar = true;
|
||||
let mut harness = Harness::builder().with_size(vec2(1280.0, 760.0)).build_ui_state(
|
||||
|ui, app: &mut CadApp| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
harness.run_steps(5);
|
||||
harness
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_gear_opens_the_options_and_the_header_keeps_collapsing() {
|
||||
let mut harness = harness();
|
||||
let header = harness.get_by_label("Draw");
|
||||
let gear = harness.get_by_label("Draw options");
|
||||
assert!(gear.rect().width() < header.rect().width() / 2.0, "the gear is a target of its own, not part of the header row's");
|
||||
|
||||
harness.get_by_label("Draw").click();
|
||||
harness.run_steps(5);
|
||||
assert!(harness.state().ui.collapsed_groups.iter().any(|g| g == "Draw"), "clicking the header collapses the category");
|
||||
harness.get_by_label("Draw").click();
|
||||
harness.run_steps(5);
|
||||
assert!(harness.state().ui.collapsed_groups.iter().all(|g| g != "Draw"), "clicking it again expands the category");
|
||||
|
||||
harness.get_by_label("Draw options").click();
|
||||
harness.run_steps(5);
|
||||
assert!(harness.query_by_role_and_label(egui::accesskit::Role::CheckBox, HIDDEN).is_some(), "the gear opens the category's tool list");
|
||||
assert!(harness.state().ui.collapsed_groups.is_empty(), "the gear itself must not collapse the category");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_options_menu_hides_a_tool_and_remembers_the_choice() {
|
||||
let mut harness = harness();
|
||||
assert!(harness.query_by_label(HIDDEN).is_some(), "the palette starts with every tool of the category");
|
||||
|
||||
harness.get_by_label("Draw options").click();
|
||||
harness.run_steps(5);
|
||||
harness.get_by_role_and_label(egui::accesskit::Role::CheckBox, HIDDEN).click();
|
||||
harness.run_steps(5);
|
||||
|
||||
assert_eq!(harness.state().ui.hidden_tools, vec!["revcloud".to_string()], "the choice is kept in the UI state");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hidden_tool_leaves_the_palette_and_show_all_puts_it_back() {
|
||||
let mut harness = harness();
|
||||
harness.state_mut().ui.hidden_tools.push(HIDDEN_CMD.to_string());
|
||||
harness.run_steps(5);
|
||||
assert!(harness.query_by_label(HIDDEN).is_none(), "a hidden tool leaves the palette");
|
||||
assert!(harness.query_by_label("Construction Line").is_some(), "the rest of the category stays");
|
||||
|
||||
harness.get_by_label("Draw options").click();
|
||||
harness.run_steps(5);
|
||||
harness.get_by_label("Show All").click();
|
||||
harness.run_steps(5);
|
||||
|
||||
assert!(harness.state().ui.hidden_tools.is_empty(), "Show all empties the list");
|
||||
assert!(harness.query_by_role_and_label(egui::accesskit::Role::Button, HIDDEN).is_some(), "the tool is back in the palette");
|
||||
}
|
||||
Reference in new issue
Block a user