mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3caced67b | |||
| d996441241 | |||
| 8a40998c2a | |||
| 924585be0a | |||
| b2c0fd71d0 | |||
| 2605261665 | |||
| 397bbf38c7 | |||
| 4db733ec68 |
Generated
+8
@@ -4366,6 +4366,7 @@ dependencies = [
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-space-mouse",
|
||||
"solidcraft-ui-registry",
|
||||
"syn 2.0.119",
|
||||
"sys-locale",
|
||||
"tiny-skia",
|
||||
@@ -4377,6 +4378,13 @@ dependencies = [
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-registry"
|
||||
version = "0.40.0-rc.3"
|
||||
dependencies = [
|
||||
"egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.40.0-rc.3"
|
||||
|
||||
@@ -28,6 +28,7 @@ solidcraft-doc = { path = "crates/doc" }
|
||||
solidcraft-io = { path = "crates/io" }
|
||||
solidcraft-engine = { path = "crates/engine" }
|
||||
solidcraft-ui-egui = { path = "crates/ui-egui" }
|
||||
solidcraft-ui-registry = { path = "crates/ui-registry" }
|
||||
solidcraft-space-mouse = { path = "crates/space-mouse" }
|
||||
solidcraft-mcp = { path = "crates/mcp" }
|
||||
solidcraft-derive = { path = "crates/derive" }
|
||||
|
||||
@@ -11,6 +11,8 @@ workspace = true
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-engine = { workspace = true }
|
||||
# The widget registry (handles, rects, counts) the UI kit publishes into.
|
||||
solidcraft-ui-registry = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
egui-wgpu = { workspace = true }
|
||||
solidcraft-gpu = { workspace = true }
|
||||
|
||||
@@ -1659,7 +1659,7 @@ fn copy_message_menu(ui: &egui::Ui, response: &egui::Response, n: usize, text: &
|
||||
.frame(ui_kit::Menu::frame())
|
||||
.open_memory(if opened { Some(egui::SetOpenCommand::Bool(true)) } else { None })
|
||||
.show(|ui| {
|
||||
ui_kit::Menu::show(ui, crate::context_menu::LIST_W, |ui| {
|
||||
ui_kit::Menu::show(ui, crate::ui_kit::LIST_W, |ui| {
|
||||
let item = ui_kit::MenuItem::new("copy_message", tr("ui.agent_chat_copy_message"), "copy");
|
||||
let row = item.show(ui);
|
||||
crate::scenario::publish_handle(&format!("agent_chat:copy_menu:{n}"), row.rect.center());
|
||||
|
||||
@@ -519,7 +519,7 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
permissions_section(app, ui);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let browsing = !crate::prefs_search::querying(ui) || app.prefs_window.section == crate::prefs::PAGE_ASSISTANT;
|
||||
let browsing = !crate::ui_kit::settings_search::querying(ui) || app.prefs_window.section == crate::prefs::PAGE_ASSISTANT;
|
||||
if app.preferences.ai_features && browsing && app.agent_chat.detection.is_none() && app.agent_chat.detecting.is_none() {
|
||||
refresh(app, &ui.ctx().clone());
|
||||
}
|
||||
@@ -561,7 +561,7 @@ fn instructions_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = ui_kit::text_area(ui, tr("ai.prefs.instructions"), id, text, 6, acp::INSTRUCTIONS_LIMIT);
|
||||
if response.has_focus() {
|
||||
// Typed text belongs to the field, not to Preferences search.
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
crate::ui_kit::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
crate::scenario::publish_target("prefs:agent_instructions", response.rect);
|
||||
ui_kit::text(ui, tr("ai.prefs.instructions_note"), ui_kit::Text::Hint("instructions"));
|
||||
@@ -658,20 +658,20 @@ fn panel_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let previous = usize::from(p.agent_chat_turn_limit != 0);
|
||||
let mut limit = previous;
|
||||
let options = [tr("ui.agent_chat_turn_unlimited"), tr("prefs.assistant_limited")];
|
||||
crate::prefs_search::note_current(ui, &options.join(" "));
|
||||
crate::ui_kit::settings_search::note_current(ui, &options.join(" "));
|
||||
let dropdown_width = ui_kit::ContentWidth::dropdown(ui, &options);
|
||||
let response =
|
||||
ui_kit::settings_control_width(ui, dropdown_width, |ui| ui_kit::dropdown(ui, "assistant_turn_limit", &mut limit, &options));
|
||||
let query = crate::prefs_search::query(ui);
|
||||
let query = crate::ui_kit::settings_search::query(ui);
|
||||
if let Some(option) = options
|
||||
.iter()
|
||||
.find(|option| option.to_lowercase() == query)
|
||||
.or_else(|| options.iter().find(|option| crate::prefs_search::texts_hit(ui, &[*option])))
|
||||
.or_else(|| options.iter().find(|option| crate::ui_kit::settings_search::texts_hit(ui, &[*option])))
|
||||
{
|
||||
crate::prefs_search::remember(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::remember(ui, option, response.rect);
|
||||
}
|
||||
for option in options {
|
||||
crate::prefs_search::follow(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, response.rect);
|
||||
}
|
||||
crate::scenario::publish_target("prefs:agent_chat_turn_limit:mode", response.rect);
|
||||
if limit != previous {
|
||||
@@ -706,7 +706,7 @@ fn pointer_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui_kit::settings_form(ui, speed_label_width, |ui| {
|
||||
ui_kit::row(ui, tr("ai.prefs.pointer_speed"), |ui| {
|
||||
let name = tr("prefs.assistant_pointer_speed");
|
||||
crate::prefs_search::note_current(ui, name);
|
||||
crate::ui_kit::settings_search::note_current(ui, name);
|
||||
let response =
|
||||
ui_kit::named_control(ui, name, |ui| crate::agent_cursor::speed_slider(ui, &mut app.ui.agent_cursor.speed));
|
||||
crate::scenario::publish_target("prefs:agent_pointer_speed", response.rect);
|
||||
@@ -717,11 +717,11 @@ fn pointer_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
response.widget_info(|| egui::WidgetInfo::slider(ui.is_enabled(), value, name));
|
||||
// The visible row owns Speed and its legacy Motion speed alias. The
|
||||
// native editor's name owns queries that only match Pointer speed.
|
||||
if !crate::prefs_search::texts_hit(ui, &[tr("ai.prefs.pointer_speed")]) {
|
||||
crate::prefs_search::remember(ui, name, response.rect);
|
||||
if !crate::ui_kit::settings_search::texts_hit(ui, &[tr("ai.prefs.pointer_speed")]) {
|
||||
crate::ui_kit::settings_search::remember(ui, name, response.rect);
|
||||
}
|
||||
for label in [tr("ai.prefs.pointer_speed"), name] {
|
||||
crate::prefs_search::follow(ui, label, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, label, response.rect);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -781,12 +781,12 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
for entry in &all {
|
||||
ui.push_id(&entry.id, |ui| {
|
||||
let detail = entry.detail();
|
||||
let gate = crate::prefs_search::block_gate(ui, &entry.name, Some(&format!("{} {detail}", entry.id)));
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let gate = crate::ui_kit::settings_search::block_gate(ui, &entry.name, Some(&format!("{} {detail}", entry.id)));
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
let mut paint = |ui: &mut egui::Ui| {
|
||||
let card = ui_kit::settings_card(ui, |ui| {
|
||||
ui.with_layout(egui::Layout::top_down(egui::Align::Min), |ui| {
|
||||
crate::prefs_search::visible(ui, &[&entry.name, &entry.id, &detail]);
|
||||
crate::ui_kit::settings_search::visible(ui, &[&entry.name, &entry.id, &detail]);
|
||||
ui.label(egui::RichText::new(&entry.name).strong().size(t.font_label).color(t.form_ink));
|
||||
ui.allocate_ui_with_layout(
|
||||
egui::vec2(ui.available_width(), t.control_height),
|
||||
@@ -851,7 +851,7 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let shown = current.map_or_else(|| tr("ui.ai_mode_global").to_owned(), |mode| mode.label().to_owned());
|
||||
let mut pick: Option<Option<Mode>> = None;
|
||||
let options: Vec<_> = std::iter::once(tr("ui.ai_mode_global")).chain(Mode::ALL.map(Mode::label)).collect();
|
||||
crate::prefs_search::note_current(ui, &options.join(" "));
|
||||
crate::ui_kit::settings_search::note_current(ui, &options.join(" "));
|
||||
let response = ui_kit::settings_control_width(ui, t.space_24 * 9.0 + t.space_4, |ui| {
|
||||
ui_kit::dropdown_menu(ui, ("sc_ai_mode", &entry.id), &shown, |ui| {
|
||||
if ui_kit::list_picker(ui, "global", tr("ui.ai_mode_global"), current.is_none()).clicked() {
|
||||
@@ -864,16 +864,16 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
})
|
||||
});
|
||||
let query = crate::prefs_search::query(ui);
|
||||
let query = crate::ui_kit::settings_search::query(ui);
|
||||
if let Some(option) = options
|
||||
.iter()
|
||||
.find(|option| option.to_lowercase() == query)
|
||||
.or_else(|| options.iter().find(|option| crate::prefs_search::texts_hit(ui, &[*option])))
|
||||
.or_else(|| options.iter().find(|option| crate::ui_kit::settings_search::texts_hit(ui, &[*option])))
|
||||
{
|
||||
crate::prefs_search::remember(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::remember(ui, option, response.rect);
|
||||
}
|
||||
for option in options {
|
||||
crate::prefs_search::follow(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, response.rect);
|
||||
}
|
||||
match pick {
|
||||
Some(Some(mode)) => {
|
||||
@@ -965,23 +965,23 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
});
|
||||
crate::scenario::publish_rect(&format!("prefs:ai_card:{}", entry.id), card.response.rect);
|
||||
let metadata = [&entry.name, &entry.id, &detail];
|
||||
if let Some(text) = metadata.iter().find(|text| crate::prefs_search::texts_hit(ui, &[*text])) {
|
||||
crate::prefs_search::remember(ui, text, card.response.rect);
|
||||
if let Some(text) = metadata.iter().find(|text| crate::ui_kit::settings_search::texts_hit(ui, &[*text])) {
|
||||
crate::ui_kit::settings_search::remember(ui, text, card.response.rect);
|
||||
}
|
||||
for text in metadata {
|
||||
crate::prefs_search::follow(ui, text, card.response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, text, card.response.rect);
|
||||
}
|
||||
};
|
||||
let show = gate.as_ref().is_none_or(|gate| gate.show);
|
||||
if show {
|
||||
paint(ui);
|
||||
} else {
|
||||
crate::prefs_search::run_hidden(ui, &entry.id, |ui| {
|
||||
crate::ui_kit::settings_search::run_hidden(ui, &entry.id, |ui| {
|
||||
paint(ui);
|
||||
});
|
||||
}
|
||||
if let Some(gate) = gate {
|
||||
crate::prefs_search::block_finish(ui, &gate, show, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, &gate, show, before);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1524,7 +1524,7 @@ mod card_tests {
|
||||
|
||||
#[test]
|
||||
fn pointer_speed_search_reveals_one_real_slider_in_all_enablement_states() {
|
||||
use crate::prefs_search::{nav, set_cursor};
|
||||
use crate::ui_kit::settings_search::{nav, set_cursor};
|
||||
for theme in ["light", "dark"] {
|
||||
for (ai, show) in [(true, true), (true, false), (false, true), (false, false)] {
|
||||
let mut h = Harness::new();
|
||||
@@ -1569,7 +1569,7 @@ mod card_tests {
|
||||
|
||||
#[test]
|
||||
fn overlapping_agent_metadata_has_one_card_target_and_keeps_fields_indexed() {
|
||||
use crate::prefs_search::{nav, set_cursor};
|
||||
use crate::ui_kit::settings_search::{nav, set_cursor};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
|
||||
@@ -148,8 +148,9 @@ use serde_json::{Value, json};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::{BrowserEntry, Document, FeatureKind};
|
||||
|
||||
use crate::context_menu::{Item, Target};
|
||||
use crate::context_menu::Target;
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::Item;
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ use solidcraft_engine::view::POINT_CLOUDS_FOLDER;
|
||||
|
||||
use super::{Actions, Row, draw_row, is_open, panel_pos, rows_start_open, toggle, toggle_folder};
|
||||
use crate::SolveApp;
|
||||
use crate::context_menu::{Item, Target};
|
||||
use crate::context_menu::Target;
|
||||
use crate::ui_kit;
|
||||
use crate::ui_kit::Item;
|
||||
|
||||
#[path = "point_cloud_tracks.rs"]
|
||||
pub(crate) mod point_cloud_tracks;
|
||||
|
||||
@@ -318,7 +318,7 @@ fn selected_row_paints_tab_soft_fill_and_accent_text_in_both_layouts() {
|
||||
let mut app = SolveApp::new(Default::default(), Default::default());
|
||||
app.ui.layout = mode;
|
||||
app.ui.tab = tab.into();
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let accent = crate::theme::TabAccent::for_tab(&Tokens::get(), tab);
|
||||
let mut row_rect = Rect::NOTHING;
|
||||
let out = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| {
|
||||
@@ -677,7 +677,7 @@ fn collapsed_rail_shares_header_hover_inset_and_expands_from_its_whole_card() {
|
||||
app.ui.tab = "SURFACE".into();
|
||||
app.preferences.timeline_left = true;
|
||||
app.preferences.timeline_left_browser_collapsed = collapsed;
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let bounds = Rect::from_min_size(Pos2::ZERO, vec2(600.0, 700.0));
|
||||
let draw = |app: &mut SolveApp, events| {
|
||||
crate::test_support::frame(&ctx, egui::RawInput { screen_rect: Some(bounds), events, ..Default::default() }, |ui| {
|
||||
|
||||
@@ -211,11 +211,7 @@ fn with_rows_starting_closed_a_designs_folders_open_with_their_arrow_or_find_in_
|
||||
"the Bodies folder stays closed"
|
||||
);
|
||||
|
||||
crate::context_menu::run_item(
|
||||
&mut h.app,
|
||||
&crate::context_menu::Item::action("ui.findBrowser", "", "").with(json!({"body":"Block"})),
|
||||
egui::Pos2::ZERO,
|
||||
);
|
||||
crate::context_menu::run_item(&mut h.app, &crate::ui_kit::Item::action("ui.findBrowser", "", "").with(json!({"body":"Block"})), egui::Pos2::ZERO);
|
||||
h.frames(2);
|
||||
assert!(super::is_open(&h.app, "c0/bodies", false), "Find in Browser opens the Bodies folder");
|
||||
assert!(
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! state; [`items`] lists them and [`run_item`] runs one, so the control channel (`ui.menu`,
|
||||
//! `ui.menuPick`) and tests reach everything a click does.
|
||||
|
||||
use crate::ui_kit::{BackingTooltipExt as _, Container, Surface};
|
||||
use crate::ui_kit::{BackingTooltipExt as _, Container, Item, LIST_W, Surface};
|
||||
use egui::{Color32, Pos2, Rect, Sense, Vec2, pos2, vec2};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
@@ -127,88 +127,6 @@ pub enum Target {
|
||||
},
|
||||
}
|
||||
|
||||
/// One menu entry: a command id (run with `params` when given, else started like a toolbar
|
||||
/// click) or a `ui.*` action. `-` is a separator, `#…` a section heading.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Item {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub shortcut: String,
|
||||
pub enabled: bool,
|
||||
pub icon: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub children: Vec<Item>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub swatch: Option<[u8; 3]>,
|
||||
/// Why the entry is off: shown on hover, and in the control channel's report.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tip: Option<String>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
/// A view action (`ui.*`).
|
||||
pub fn action(id: &str, label: &str, icon: &str) -> Item {
|
||||
act(id, label, icon)
|
||||
}
|
||||
pub fn sep() -> Item {
|
||||
Item {
|
||||
id: "-".into(),
|
||||
label: String::new(),
|
||||
shortcut: String::new(),
|
||||
enabled: false,
|
||||
icon: String::new(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
pub(crate) fn heading(label: &str) -> Item {
|
||||
Item {
|
||||
id: format!("#{label}"),
|
||||
label: label.into(),
|
||||
shortcut: String::new(),
|
||||
enabled: false,
|
||||
icon: String::new(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
fn is_sep(&self) -> bool {
|
||||
self.id == "-"
|
||||
}
|
||||
fn is_heading(&self) -> bool {
|
||||
self.id.starts_with('#')
|
||||
}
|
||||
pub fn with(mut self, params: Value) -> Item {
|
||||
self.params = Some(params);
|
||||
self
|
||||
}
|
||||
pub fn on_if(self, enabled: bool) -> Item {
|
||||
self.on(enabled)
|
||||
}
|
||||
pub(crate) fn on(mut self, enabled: bool) -> Item {
|
||||
self.enabled &= enabled;
|
||||
self
|
||||
}
|
||||
/// Off, saying why, when there is a reason.
|
||||
pub(crate) fn off_because(mut self, why: Option<String>) -> Item {
|
||||
if let Some(why) = why {
|
||||
self.enabled = false;
|
||||
self.tip = Some(why);
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn key(mut self, k: &str) -> Item {
|
||||
self.shortcut = k.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A command entry: label, icon, shortcut and enablement come from the registry.
|
||||
pub(crate) fn cmd(app: &SolveApp, id: &str, label: &str) -> Item {
|
||||
let info = solidcraft_engine::find_command(id).map(|c| c.info(&app.session));
|
||||
@@ -232,17 +150,7 @@ pub(crate) fn cmd(app: &SolveApp, id: &str, label: &str) -> Item {
|
||||
|
||||
/// A view action (`ui.*`).
|
||||
pub(crate) fn act(id: &str, label: &str, icon: &str) -> Item {
|
||||
Item {
|
||||
id: id.into(),
|
||||
label: label.into(),
|
||||
shortcut: String::new(),
|
||||
enabled: true,
|
||||
icon: icon.into(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
Item::action(id, label, icon)
|
||||
}
|
||||
|
||||
/// Menu state kept between frames. The anchor is `app.viewport.context_menu`.
|
||||
@@ -2535,9 +2443,6 @@ fn list_place(c: Pos2, h: f32, screen: Rect) -> Pos2 {
|
||||
pos2((c.x - RX - 12.0 - LIST_W).max(screen.left() + 4.0), y)
|
||||
}
|
||||
|
||||
/// Width of list menus.
|
||||
pub const LIST_W: f32 = 250.0;
|
||||
|
||||
/// Where a menu of `size` opened at `at` goes: right of and below the point (`gap` away) when it
|
||||
/// fits in `screen`, flipped to the left or above it when it would run past that edge, and
|
||||
/// against the far edge when it fits on neither side. A flipped menu ends at the cursor, so it
|
||||
|
||||
@@ -753,7 +753,7 @@ fn label_marked(k: &ConstraintKind, radius_mark: bool, expr: Option<&str>, drive
|
||||
let plain = expr.is_none_or(|e| {
|
||||
let e = e.trim();
|
||||
let e = ["mm", "cm", "in", "ft", "m", "deg", "rad", "°"].iter().find_map(|unit| e.strip_suffix(unit)).unwrap_or(e);
|
||||
crate::prefs::parse_number(e).is_some()
|
||||
crate::ui_kit::parse_number(e).is_some()
|
||||
});
|
||||
let text = if plain { num } else { format!("fx: {num}") };
|
||||
if driven { format!("({text})") } else { text }
|
||||
|
||||
@@ -1188,7 +1188,7 @@ pub fn tabs(app: &mut SolveApp, ui: &mut egui::Ui, r: Rect, x0: f32, x1: f32) {
|
||||
crate::context_menu::menu_frame(ui, |ui| {
|
||||
let mut chosen = None;
|
||||
for (id, label) in items {
|
||||
let mut item = crate::context_menu::Item::action(id, label, "");
|
||||
let mut item = crate::ui_kit::Item::action(id, label, "");
|
||||
if id == "ui.closeTab" && i == app.docs.active {
|
||||
item = item.key("Ctrl+W");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use syn::{Expr, Item, Lit};
|
||||
const ALLOWED_LITERALS: &[(&str, usize, &str)] = &[
|
||||
("dialogs.rs", 2, "preview spinners: graphic diameter, not typography"),
|
||||
("viewport.rs", 1, "viewport status spinner: graphic diameter, not typography"),
|
||||
("ui_kit/settings_search.rs", 2, "hidden off-screen areas that measure search matches: never drawn"),
|
||||
];
|
||||
|
||||
fn test_only(attrs: &[syn::Attribute]) -> bool {
|
||||
|
||||
@@ -147,14 +147,14 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context, anchor: egui::Pos2) {
|
||||
.into_iter()
|
||||
.filter(|(id, ..)| *id != "install_cli" || cfg!(target_os = "macos"))
|
||||
{
|
||||
let item = crate::context_menu::Item::action(id, label, icon).on_if(available(app, id));
|
||||
let item = crate::ui_kit::Item::action(id, label, icon).on_if(available(app, id));
|
||||
if crate::context_menu::list_row(ui, &item).is_some() {
|
||||
pick = Some(id);
|
||||
}
|
||||
// A layout without the version in the application bar lists it here.
|
||||
if id == "about" && app.header.version == crate::toolbar::VersionPlace::HelpMenu {
|
||||
let label = crate::i18n::format("help.version", &[("version", crate::update::current_version())]);
|
||||
if crate::context_menu::list_row(ui, &crate::context_menu::Item::action("version", &label, "update")).is_some() {
|
||||
if crate::context_menu::list_row(ui, &crate::ui_kit::Item::action("version", &label, "update")).is_some() {
|
||||
pick = Some("version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::test_support::sample_app;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::browser;
|
||||
use crate::context_menu::{self, Item, Target};
|
||||
use crate::context_menu::{self, Target};
|
||||
use crate::ui_kit::Item;
|
||||
use crate::{Services, SolveApp};
|
||||
use serde_json::json;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
@@ -578,7 +578,7 @@ pub fn reset(app: &mut SolveApp, id: Option<&str>) {
|
||||
/// reset; a key another command uses is reported and taken over only on request.
|
||||
pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let ctx = ui.ctx().clone();
|
||||
let filtering = crate::prefs_search::querying(ui);
|
||||
let filtering = crate::ui_kit::settings_search::querying(ui);
|
||||
// A Preferences search lists the matching shortcuts too, and one recorded from there takes
|
||||
// the key like one on this page (#10043). Only a search field that has the keyboard keeps
|
||||
// its keys: going back to it to type stops the recording.
|
||||
@@ -708,8 +708,8 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let label = app.tr_label(c.label);
|
||||
let group = shortcut_group(c);
|
||||
let hit = if filtering {
|
||||
crate::prefs_search::shows_all(ui)
|
||||
|| crate::prefs_search::matches_text(
|
||||
crate::ui_kit::settings_search::shows_all(ui)
|
||||
|| crate::ui_kit::settings_search::matches_text(
|
||||
&q,
|
||||
&[c.label, c.id, label, &group]
|
||||
.into_iter()
|
||||
@@ -761,8 +761,8 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui_kit::section_heading(ui, group);
|
||||
let heading = crate::scenario::rect_of(&format!("dialog:heading:{group}"));
|
||||
if let Some(rect) = heading {
|
||||
crate::prefs_search::remember(ui, group, rect);
|
||||
crate::prefs_search::follow(ui, group, rect);
|
||||
crate::ui_kit::settings_search::remember(ui, group, rect);
|
||||
crate::ui_kit::settings_search::follow(ui, group, rect);
|
||||
}
|
||||
for (command, keys, label) in rows {
|
||||
if let Some(chosen) = shortcut_row(app, ui, command, keys, label, filtering) {
|
||||
@@ -777,9 +777,9 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// Collapsed matches still participate in Up/Down and Enter. Enter lands on
|
||||
// the real group or command, where follow() reveals its complete row.
|
||||
for (group, rows) in &groups {
|
||||
crate::prefs_search::remember(ui, group, rect);
|
||||
crate::ui_kit::settings_search::remember(ui, group, rect);
|
||||
for (command, keys, label) in rows {
|
||||
crate::prefs_search::remember(ui, &shortcut_search_label(command, label, keys), rect);
|
||||
crate::ui_kit::settings_search::remember(ui, &shortcut_search_label(command, label, keys), rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,13 +915,13 @@ fn shortcut_row(
|
||||
let height = if beside { label_height.max(control_height) } else { label_height + control_height + t.space_4 } + t.space_8;
|
||||
let (rect, _) = ui.allocate_exact_size(egui::vec2(width, height), egui::Sense::hover());
|
||||
let search_label = shortcut_search_label(command, label, keys);
|
||||
crate::prefs_search::follow(ui, &search_label, rect);
|
||||
crate::ui_kit::settings_search::follow(ui, &search_label, rect);
|
||||
if !filtering && !capturing && !ui.is_rect_visible(rect) {
|
||||
return None;
|
||||
}
|
||||
if filtering {
|
||||
crate::prefs_search::reveal(ui);
|
||||
crate::prefs_search::push_force(ui);
|
||||
crate::ui_kit::settings_search::reveal(ui);
|
||||
crate::ui_kit::settings_search::push_force(ui);
|
||||
}
|
||||
let mut action = None;
|
||||
ui.push_id(command.id, |ui| {
|
||||
@@ -999,10 +999,10 @@ fn shortcut_row(
|
||||
});
|
||||
}
|
||||
});
|
||||
crate::prefs_search::remember(ui, &search_label, rect);
|
||||
crate::ui_kit::settings_search::remember(ui, &search_label, rect);
|
||||
t.secondary_hline(ui.painter(), rect.x_range(), rect.bottom(), t.dialog_rule);
|
||||
if filtering {
|
||||
crate::prefs_search::pop_force(ui);
|
||||
crate::ui_kit::settings_search::pop_force(ui);
|
||||
}
|
||||
action
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! right of the viewport, the command palette and a bottom or left-sidebar feature timeline.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::ui_kit::{Container, Surface, recent_colors};
|
||||
|
||||
pub mod access;
|
||||
pub mod agent_chat;
|
||||
@@ -209,7 +209,6 @@ pub(crate) mod plugins;
|
||||
pub mod point_clouds;
|
||||
pub mod prefs;
|
||||
pub mod prefs_notice;
|
||||
mod prefs_search;
|
||||
#[cfg(test)]
|
||||
mod preselect_tests;
|
||||
pub mod preview;
|
||||
@@ -222,7 +221,6 @@ mod print_face_ui;
|
||||
pub(crate) mod quick_measure;
|
||||
#[cfg(test)]
|
||||
mod quick_measure_tests;
|
||||
pub(crate) mod recent_colors;
|
||||
pub mod recovery_ui;
|
||||
pub mod ref_images;
|
||||
pub(crate) mod refused_part;
|
||||
@@ -837,6 +835,12 @@ impl Drop for SolveApp {
|
||||
}
|
||||
|
||||
impl SolveApp {
|
||||
/// Publish this frame's tab accent from the open tab, the sketch being edited and the active
|
||||
/// tool or dialog, the three values [`theme::TabAccent::publish`] takes.
|
||||
pub(crate) fn publish_tab_accent(&self, ctx: &egui::Context) {
|
||||
theme::TabAccent::publish(&self.ui.tab, self.session.active_sketch.is_some(), self.tool.is_some() || self.dialog.is_some(), ctx);
|
||||
}
|
||||
|
||||
pub fn new(mut session: Session, services: Services) -> Self {
|
||||
#[cfg(test)]
|
||||
let image_host = Some(solidcraft_engine::render::job::CpuHost::cooperative());
|
||||
@@ -3227,6 +3231,7 @@ impl SolveApp {
|
||||
|
||||
/// Per-frame logic before layout.
|
||||
pub fn logic(&mut self, ctx: &egui::Context) {
|
||||
params_dialog::install_expressions(ctx);
|
||||
// Shortcuts run here, before `ui` installs the language scope: a tool started by its
|
||||
// key must build its translated hint in the user's language, not in English.
|
||||
let _language = crate::i18n::scope(&self.preferences.language);
|
||||
@@ -3276,7 +3281,7 @@ impl SolveApp {
|
||||
theme::apply(ctx);
|
||||
self.styled = true;
|
||||
}
|
||||
theme::TabAccent::publish(self, ctx);
|
||||
self.publish_tab_accent(ctx);
|
||||
if !self.fitted {
|
||||
self.fit_view();
|
||||
self.fitted = true;
|
||||
@@ -3403,6 +3408,7 @@ impl SolveApp {
|
||||
|
||||
/// Lay out the whole window.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
params_dialog::install_expressions(ui.ctx());
|
||||
prefs::prepare_blur(self, ui.ctx());
|
||||
render_workspace::displayed::begin(self);
|
||||
let blur_source = !self.home.open && !render_workspace::result_active(self);
|
||||
@@ -3520,7 +3526,7 @@ impl SolveApp {
|
||||
if !floating && !self.embedded {
|
||||
toolbar::toolbar(self, ui);
|
||||
}
|
||||
theme::TabAccent::publish(self, ui.ctx());
|
||||
self.publish_tab_accent(ui.ctx());
|
||||
browser::publish_row_prefs(self, ui.ctx());
|
||||
if !floating {
|
||||
agent_chat::show(self, ui);
|
||||
@@ -3573,7 +3579,7 @@ impl SolveApp {
|
||||
self.show_workspace(&mut viewport_ui);
|
||||
shell::paint_top_corners(ui.painter(), full, theme::Tokens::get().app_bar);
|
||||
toolbar::toolbar(self, ui);
|
||||
theme::TabAccent::publish(self, ui.ctx());
|
||||
self.publish_tab_accent(ui.ctx());
|
||||
}
|
||||
if floating && show_timeline && !left_timeline {
|
||||
timeline::timeline(self, ui);
|
||||
|
||||
@@ -292,7 +292,7 @@ mod tests {
|
||||
(app, component, occurrence, folder, moved)
|
||||
}
|
||||
|
||||
fn menu(app: &SolveApp, target: Target) -> Option<context_menu::Item> {
|
||||
fn menu(app: &SolveApp, target: Target) -> Option<crate::ui_kit::Item> {
|
||||
context_menu::entries(app, &target).into_iter().find(|i| i.id == "ui.relinkSource")
|
||||
}
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ impl PropertyDraft {
|
||||
}
|
||||
|
||||
fn number(value: &str, field: &str) -> Result<f64, String> {
|
||||
crate::prefs::parse_number(value).ok_or_else(|| format!("{field}: {}", tr("material.editor.number_required")))
|
||||
crate::ui_kit::parse_number(value).ok_or_else(|| format!("{field}: {}", tr("material.editor.number_required")))
|
||||
}
|
||||
|
||||
fn field(ui: &mut egui::Ui, label: &str, id: &str, value: &mut String, unit: &str) {
|
||||
|
||||
@@ -7,7 +7,8 @@ use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
|
||||
use crate::context_menu::{self, Item, Target};
|
||||
use crate::context_menu::{self, Target};
|
||||
use crate::ui_kit::Item;
|
||||
use crate::viewport::Hit;
|
||||
use crate::{Services, SolveApp};
|
||||
|
||||
@@ -2053,7 +2054,7 @@ fn a_flyout_inside_a_flyout_that_opened_to_the_left_opens_to_the_left() {
|
||||
use crate::scenario::rect_of;
|
||||
let (mut h, c) = component_menu();
|
||||
let right = h.ctx.content_rect().right();
|
||||
context_menu::open_for(&mut h.app, pos2(right - context_menu::LIST_W - 50.0, 60.0), Target::Component { id: c });
|
||||
context_menu::open_for(&mut h.app, pos2(right - crate::ui_kit::LIST_W - 50.0, 60.0), Target::Component { id: c });
|
||||
h.frames(2);
|
||||
h.step(&json!({"move":{"handle":"menu:ui.group.component"}})).unwrap();
|
||||
h.frames(2);
|
||||
|
||||
@@ -27,8 +27,9 @@ use egui::{Align2, Color32, FontId, Mesh, Painter, Pos2, Rect, Sense, Shape, Str
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::context_menu::{Item, act, cmd};
|
||||
use crate::context_menu::{act, cmd};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::Item;
|
||||
|
||||
/// Most tools one ring may hold.
|
||||
pub(crate) const MAX_TOOLS: usize = 12;
|
||||
|
||||
@@ -21,22 +21,6 @@ use crate::prefs::{OrbitAround, pivot_under};
|
||||
use crate::space_mouse::{BridgeMotion, Motion};
|
||||
use crate::viewport::{Hit, NavMode, axis_rect, nearest_quarter_turn, pick, pick_cached, projection};
|
||||
|
||||
/// Take a predominantly horizontal wheel gesture for a hovered scroll viewport.
|
||||
/// Leave vertical input for its enclosing page, and leave horizontal input
|
||||
/// unconsumed when this viewport is already at the requested end.
|
||||
pub(crate) fn take_horizontal_wheel(ui: &egui::Ui, offset: f32, max_offset: f32) -> f32 {
|
||||
ui.input_mut(|input| {
|
||||
let delta = input.smooth_scroll_delta;
|
||||
if delta.x == 0.0 || delta.x.abs() <= delta.y.abs() {
|
||||
return 0.0;
|
||||
}
|
||||
if (offset - delta.x).clamp(0.0, max_offset) != offset {
|
||||
input.smooth_scroll_delta.x = 0.0;
|
||||
}
|
||||
delta.x
|
||||
})
|
||||
}
|
||||
|
||||
/// What a page's camera may do.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct NavCaps {
|
||||
@@ -2062,7 +2046,8 @@ mod tests {
|
||||
];
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let dirs = [root.join("src"), root.join("../../apps/solidcraft/src"), root.join("../../apps/solidcraft-web/src")];
|
||||
let allowed = ["navigation.rs"];
|
||||
// The kit's wheel.rs scrolls strips (a horizontal wheel over a hovered scroll area), not cameras.
|
||||
let allowed = ["navigation.rs", "wheel.rs"];
|
||||
let mut offenders = Vec::new();
|
||||
let mut scanned = 0;
|
||||
let mut stack: Vec<std::path::PathBuf> = dirs.iter().filter(|d| d.is_dir()).cloned().collect();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! Stored navigation slots and their catalogue. Painting lives in `navigation_bar_view`.
|
||||
use crate::{SolveApp, navigation::NavCaps};
|
||||
use crate::{
|
||||
SolveApp,
|
||||
navigation::NavCaps,
|
||||
ui_kit::{NavigationEdge, NavigationIdle, NavigationKind, NavigationSize},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -34,18 +38,9 @@ pub(crate) fn visual_style_icon(style: u8) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Kind {
|
||||
Action,
|
||||
Tool,
|
||||
Toggle,
|
||||
Menu,
|
||||
Separator,
|
||||
}
|
||||
|
||||
pub(crate) struct Slot {
|
||||
pub id: &'static str,
|
||||
pub kind: Kind,
|
||||
pub kind: NavigationKind,
|
||||
pub icon: &'static str,
|
||||
pub label: &'static str,
|
||||
pub description: &'static str,
|
||||
@@ -56,7 +51,7 @@ macro_rules! slot {
|
||||
($id:literal, $kind:ident, $icon:literal, $command:expr) => {
|
||||
Slot {
|
||||
id: $id,
|
||||
kind: Kind::$kind,
|
||||
kind: NavigationKind::$kind,
|
||||
icon: $icon,
|
||||
label: concat!("navbar.", $id),
|
||||
description: concat!("navbar.description.", $id),
|
||||
@@ -124,30 +119,6 @@ pub enum Position {
|
||||
Right,
|
||||
}
|
||||
|
||||
/// The edge of the 3D view the bar sits on (#8183): the bottom, or the top, under the ribbon.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Edge {
|
||||
#[default]
|
||||
Bottom,
|
||||
Top,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Size {
|
||||
Compact,
|
||||
#[default]
|
||||
Regular,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
// Logical points: egui's UI zoom applies the preference's scale to these dimensions.
|
||||
pub(crate) fn points(self) -> f32 {
|
||||
crate::ui_kit::NavigationMetrics::for_size(self).button
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Labels {
|
||||
@@ -156,15 +127,6 @@ pub enum Labels {
|
||||
Labels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Idle {
|
||||
Always,
|
||||
#[default]
|
||||
Fade,
|
||||
Hide,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MenuDisplay {
|
||||
@@ -183,10 +145,10 @@ pub struct NavigationBar {
|
||||
pub slots: Vec<String>,
|
||||
pub slot_options: std::collections::BTreeMap<String, SlotOptions>,
|
||||
pub position: Position,
|
||||
pub edge: Edge,
|
||||
pub size: Size,
|
||||
pub edge: NavigationEdge,
|
||||
pub size: NavigationSize,
|
||||
pub labels: Labels,
|
||||
pub idle: Idle,
|
||||
pub idle: NavigationIdle,
|
||||
}
|
||||
|
||||
impl Default for NavigationBar {
|
||||
@@ -198,10 +160,10 @@ impl Default for NavigationBar {
|
||||
.collect(),
|
||||
slot_options: Default::default(),
|
||||
position: Position::default(),
|
||||
edge: Edge::default(),
|
||||
size: Size::default(),
|
||||
edge: NavigationEdge::default(),
|
||||
size: NavigationSize::default(),
|
||||
labels: Labels::default(),
|
||||
idle: Idle::default(),
|
||||
idle: NavigationIdle::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +195,7 @@ impl<'de> Deserialize<'de> for NavigationBar {
|
||||
}
|
||||
if let Some(options) = value.get("slot_options").and_then(Value::as_object) {
|
||||
for (id, option) in options.iter().take(MAX_SLOTS) {
|
||||
if id.len() <= 128 && find(id).is_some_and(|slot| slot.kind == Kind::Menu) {
|
||||
if id.len() <= 128 && find(id).is_some_and(|slot| slot.kind == NavigationKind::Menu) {
|
||||
let shows = option.get("shows").cloned().and_then(|v| serde_json::from_value(v).ok()).unwrap_or_default();
|
||||
bar.slot_options.insert(id.clone(), SlotOptions { shows });
|
||||
}
|
||||
@@ -274,10 +236,10 @@ pub(crate) fn validate(value: &Value) -> Result<(), String> {
|
||||
})
|
||||
}),
|
||||
"position" => serde_json::from_value::<Position>(value.clone()).is_ok(),
|
||||
"edge" => serde_json::from_value::<Edge>(value.clone()).is_ok(),
|
||||
"size" => serde_json::from_value::<Size>(value.clone()).is_ok(),
|
||||
"edge" => serde_json::from_value::<NavigationEdge>(value.clone()).is_ok(),
|
||||
"size" => serde_json::from_value::<NavigationSize>(value.clone()).is_ok(),
|
||||
"labels" => serde_json::from_value::<Labels>(value.clone()).is_ok(),
|
||||
"idle" => serde_json::from_value::<Idle>(value.clone()).is_ok(),
|
||||
"idle" => serde_json::from_value::<NavigationIdle>(value.clone()).is_ok(),
|
||||
_ => return Err(format!("unknown navigation_bar field `{key}`")),
|
||||
};
|
||||
if !valid {
|
||||
|
||||
@@ -38,7 +38,7 @@ fn slot_catalogue_uses_registered_commands_and_existing_icons() {
|
||||
Some(command) => assert!(solidcraft_engine::find_command(command).is_some(), "{}", slot.id),
|
||||
None => assert!(["projection", "display", "separator"].contains(&slot.id)),
|
||||
}
|
||||
if slot.kind != Kind::Separator {
|
||||
if slot.kind != NavigationKind::Separator {
|
||||
assert!(!crate::icon_svg::resolve(slot.icon).placeholder, "{}: {}", slot.id, slot.icon);
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ fn measured_bar_drops_labels_then_overflows_in_order_without_compass_overlap() {
|
||||
#[test]
|
||||
fn measured_bar_honours_size_position_and_ignores_unknown_slots() {
|
||||
let canvas = Rect::from_min_size(pos2(20.0, 30.0), vec2(700.0, 500.0));
|
||||
for size in [Size::Compact, Size::Regular] {
|
||||
for size in [NavigationSize::Compact, NavigationSize::Regular] {
|
||||
for position in [Position::Centre, Position::Left, Position::Right] {
|
||||
let prefs: NavigationBar = serde_json::from_value(json!({"slots":["future", "pan", "fit"], "size":size, "position":position})).unwrap();
|
||||
let bar = measure(canvas, &prefs, NavCaps::MODEL, |_| 0.0);
|
||||
@@ -156,20 +156,20 @@ fn a_bar_on_the_top_edge_sits_under_the_top_and_keeps_the_corner_controls_below_
|
||||
use crate::ui_kit::NAV_BOTTOM;
|
||||
// Older settings have no edge and keep the bar at the bottom; a bad value recovers the same way.
|
||||
let old: NavigationBar = serde_json::from_value(json!({"position": "right"})).unwrap();
|
||||
assert_eq!(old.edge, Edge::Bottom);
|
||||
assert_eq!(old.edge, NavigationEdge::Bottom);
|
||||
let bad: NavigationBar = serde_json::from_value(json!({"edge": "sideways"})).unwrap();
|
||||
assert_eq!(bad.edge, Edge::Bottom);
|
||||
assert_eq!(bad.edge, NavigationEdge::Bottom);
|
||||
assert!(validate(&json!({"edge": "sideways"})).is_err());
|
||||
assert!(validate(&json!({"edge": "top"})).is_ok());
|
||||
let canvas = Rect::from_min_size(pos2(20.0, 30.0), vec2(900.0, 600.0));
|
||||
for position in [Position::Centre, Position::Left, Position::Right] {
|
||||
for edge in [Edge::Bottom, Edge::Top] {
|
||||
for edge in [NavigationEdge::Bottom, NavigationEdge::Top] {
|
||||
let prefs: NavigationBar = serde_json::from_value(json!({"position": position, "edge": edge})).unwrap();
|
||||
assert_eq!(serde_json::to_value(&prefs).unwrap()["edge"], if edge == Edge::Top { "top" } else { "bottom" });
|
||||
assert_eq!(serde_json::to_value(&prefs).unwrap()["edge"], if edge == NavigationEdge::Top { "top" } else { "bottom" });
|
||||
let bar = measure(canvas, &prefs, NavCaps::MODEL, |_| 50.0);
|
||||
match edge {
|
||||
Edge::Bottom => assert_eq!(bar.rect.bottom(), canvas.bottom() - NAV_BOTTOM),
|
||||
Edge::Top => assert_eq!(bar.rect.top(), canvas.top() + NAV_BOTTOM),
|
||||
NavigationEdge::Bottom => assert_eq!(bar.rect.bottom(), canvas.bottom() - NAV_BOTTOM),
|
||||
NavigationEdge::Top => assert_eq!(bar.rect.top(), canvas.top() + NAV_BOTTOM),
|
||||
}
|
||||
for corner in [egui::Vec2::ZERO, vec2(120.0, 32.0)] {
|
||||
let layout =
|
||||
@@ -187,7 +187,7 @@ fn a_bar_on_the_top_edge_sits_under_the_top_and_keeps_the_corner_controls_below_
|
||||
#[test]
|
||||
fn navigation_slot_visuals_never_use_focus_as_a_fill() {
|
||||
use crate::ui_kit::{NavigationFill, navigation_visual};
|
||||
for kind in [Kind::Action, Kind::Tool, Kind::Toggle, Kind::Menu] {
|
||||
for kind in [NavigationKind::Action, NavigationKind::Tool, NavigationKind::Toggle, NavigationKind::Menu] {
|
||||
for keyboard_focus in [false, true] {
|
||||
assert_eq!(
|
||||
navigation_visual(kind, false, false, false, keyboard_focus),
|
||||
@@ -202,8 +202,8 @@ fn navigation_slot_visuals_never_use_focus_as_a_fill() {
|
||||
);
|
||||
}
|
||||
let active_fill = match kind {
|
||||
Kind::Action => NavigationFill::None,
|
||||
Kind::Menu => NavigationFill::Press,
|
||||
NavigationKind::Action => NavigationFill::None,
|
||||
NavigationKind::Menu => NavigationFill::Press,
|
||||
_ => NavigationFill::Active,
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -236,7 +236,7 @@ fn navigation_slot_pointer_clicks_release_focus_and_leave_no_fill() {
|
||||
fill
|
||||
}
|
||||
|
||||
for kind in [Kind::Action, Kind::Tool, Kind::Toggle] {
|
||||
for kind in [NavigationKind::Action, NavigationKind::Tool, NavigationKind::Toggle] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let bounds = Rect::from_min_size(pos2(20.0, 30.0), vec2(26.0, 26.0));
|
||||
let draw = |events, selected| {
|
||||
@@ -271,7 +271,7 @@ fn navigation_slot_pointer_clicks_release_focus_and_leave_no_fill() {
|
||||
assert!(!released.1, "{kind:?}: pointer activation must surrender focus");
|
||||
let away = draw(vec![Event::PointerMoved(pos2(200.0, 200.0))], false);
|
||||
assert_eq!(fill(&away.0, bounds), None, "{kind:?}: no lingering fill");
|
||||
if kind != Kind::Action {
|
||||
if kind != NavigationKind::Action {
|
||||
let active = draw(vec![], true);
|
||||
assert_eq!(fill(&active.0, bounds), Some(Tokens::get().selected_bg));
|
||||
draw(crate::test_support::pointer(bounds.center(), true), true);
|
||||
@@ -399,7 +399,7 @@ fn navigation_preview_and_viewport_share_the_row_painter() {
|
||||
&& live != preview
|
||||
{
|
||||
let first = live.vertices.first().unwrap().pos;
|
||||
assert!(bar.slots.iter().any(|(slot, rect)| slot.kind == Kind::Menu && rect.contains(first)));
|
||||
assert!(bar.slots.iter().any(|(slot, rect)| slot.kind == NavigationKind::Menu && rect.contains(first)));
|
||||
let delta = preview.vertices.first().unwrap().pos - first;
|
||||
assert_eq!(delta.y, 0.0);
|
||||
assert_eq!(delta.x.abs(), crate::ui_kit::NAV_BORDER, "editing menus include their CSS transparent border");
|
||||
@@ -484,8 +484,10 @@ fn navigation_context_menus_customise_hide_reset_and_remove() {
|
||||
#[test]
|
||||
fn navigation_board_metrics_and_state_colors_are_shared_in_both_themes() {
|
||||
use crate::ui_kit::*;
|
||||
assert_eq!(Size::default(), Size::Regular);
|
||||
for (size, button, icon, height, radius, button_radius) in [(Size::Regular, 32.0, 18.0, 40.0, 10, 7), (Size::Compact, 26.0, 16.0, 34.0, 9, 6)] {
|
||||
assert_eq!(NavigationSize::default(), NavigationSize::Regular);
|
||||
for (size, button, icon, height, radius, button_radius) in
|
||||
[(NavigationSize::Regular, 32.0, 18.0, 40.0, 10, 7), (NavigationSize::Compact, 26.0, 16.0, 34.0, 9, 6)]
|
||||
{
|
||||
let metrics = NavigationMetrics::for_size(size);
|
||||
assert_eq!(
|
||||
(metrics.button, metrics.icon, metrics.height(), metrics.radius, metrics.button_radius),
|
||||
@@ -513,7 +515,7 @@ fn navigation_board_metrics_and_state_colors_are_shared_in_both_themes() {
|
||||
assert_eq!(frame.shadow, Container::menu_frame().shadow, "navigation inherits the shared popup elevation");
|
||||
}
|
||||
assert_eq!((NAV_POPUP_WIDTH, NAV_CONTEXT_WIDTH, NAV_MORE_WIDTH, NAV_POPUP_GAP), (244.0, 232.0, 200.0, 6.0));
|
||||
for (idle, opacity) in [(Idle::Always, 1.0), (Idle::Fade, 0.45), (Idle::Hide, 0.0)] {
|
||||
for (idle, opacity) in [(NavigationIdle::Always, 1.0), (NavigationIdle::Fade, 0.45), (NavigationIdle::Hide, 0.0)] {
|
||||
assert_eq!(navigation_opacity(idle, false), opacity);
|
||||
assert_eq!(navigation_opacity(idle, true), 1.0);
|
||||
}
|
||||
@@ -585,12 +587,12 @@ fn navigation_board_position_aliases_migrate_and_editor_alignment_follows_positi
|
||||
let bar: NavigationBar = serde_json::from_value(json!({"position":legacy,"slots":["orbit","pan","fit","look_at","visual_style"]})).unwrap();
|
||||
assert_eq!(bar.position, position);
|
||||
assert_eq!(serde_json::to_value(&bar).unwrap()["position"], current);
|
||||
for size in [Size::Compact, Size::Regular] {
|
||||
for size in [NavigationSize::Compact, NavigationSize::Regular] {
|
||||
let bar = NavigationBar { size, ..bar.clone() };
|
||||
let preview = editor_layout(stage, &bar, |_| 0.0);
|
||||
let live = measure(stage, &bar, NavCaps::MODEL, |_| 0.0);
|
||||
assert_eq!(preview.rect.bottom(), stage.bottom() - NAV_BORDER - NAV_STAGE_INSET);
|
||||
let top = editor_layout(stage, &NavigationBar { edge: crate::navigation_bar::Edge::Top, ..bar.clone() }, |_| 0.0);
|
||||
let top = editor_layout(stage, &NavigationBar { edge: NavigationEdge::Top, ..bar.clone() }, |_| 0.0);
|
||||
assert_eq!(top.rect.top(), stage.top() + NAV_BORDER + NAV_STAGE_INSET);
|
||||
assert_eq!(preview.rect.height(), NavigationMetrics::for_size(size).height());
|
||||
match position {
|
||||
@@ -601,7 +603,7 @@ fn navigation_board_position_aliases_migrate_and_editor_alignment_follows_positi
|
||||
assert_eq!(preview.slots.iter().map(|(slot, _)| slot.id).collect::<Vec<_>>(), bar.slots);
|
||||
for ((slot, rect), (live_slot, live_rect)) in preview.slots.iter().zip(&live.slots) {
|
||||
assert_eq!(slot.id, live_slot.id);
|
||||
assert_eq!(rect.width(), live_rect.width() + if slot.kind == Kind::Menu { 2.0 * NAV_BORDER } else { 0.0 });
|
||||
assert_eq!(rect.width(), live_rect.width() + if slot.kind == NavigationKind::Menu { 2.0 * NAV_BORDER } else { 0.0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Measured single-row navigation bar. Commands own all view changes.
|
||||
use crate::navigation::NavCaps;
|
||||
use crate::navigation_bar::{self, Edge, Idle, Kind, Labels, MenuDisplay, NavigationBar, Position, Slot};
|
||||
use crate::navigation_bar::{self, Labels, MenuDisplay, NavigationBar, Position, Slot};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{self, Container, Surface};
|
||||
use crate::ui_kit::{self, Container, NavigationEdge, NavigationIdle, NavigationKind, Surface};
|
||||
use crate::{SolveApp, viewport::NavMode};
|
||||
use egui::{Align2, Rect, pos2, vec2};
|
||||
use serde_json::json;
|
||||
@@ -39,9 +39,9 @@ fn measure_row(content: Rect, prefs: &NavigationBar, caps: NavCaps, label_width:
|
||||
return result;
|
||||
}
|
||||
let width = |slot: &Slot, labels, values| {
|
||||
if slot.kind == Kind::Separator {
|
||||
if slot.kind == NavigationKind::Separator {
|
||||
NAV_SEPARATOR
|
||||
} else if slot.kind == Kind::Menu {
|
||||
} else if slot.kind == NavigationKind::Menu {
|
||||
metrics.icon
|
||||
+ if editing { 2.0 * NAV_BORDER } else { 0.0 }
|
||||
+ NAV_MENU_LEFT
|
||||
@@ -91,8 +91,8 @@ fn measure_row(content: Rect, prefs: &NavigationBar, caps: NavCaps, label_width:
|
||||
};
|
||||
// The same gap from either edge of the view.
|
||||
let top = match prefs.edge {
|
||||
navigation_bar::Edge::Bottom => content.bottom() - height - NAV_BOTTOM,
|
||||
navigation_bar::Edge::Top => content.top() + NAV_BOTTOM,
|
||||
NavigationEdge::Bottom => content.bottom() - height - NAV_BOTTOM,
|
||||
NavigationEdge::Top => content.top() + NAV_BOTTOM,
|
||||
};
|
||||
result.rect = Rect::from_min_size(pos2(left, top), vec2(row_width, height));
|
||||
let mut x = left + inset;
|
||||
@@ -122,8 +122,8 @@ pub(crate) fn editor_layout(stage: Rect, prefs: &NavigationBar, label_width: imp
|
||||
};
|
||||
// The same inset from the top or bottom edge as from the side, as in the live view.
|
||||
let y = match prefs.edge {
|
||||
navigation_bar::Edge::Top => stage.top() + ui_kit::NAV_BORDER + ui_kit::NAV_STAGE_INSET,
|
||||
navigation_bar::Edge::Bottom => stage.bottom() - ui_kit::NAV_BORDER - ui_kit::NAV_STAGE_INSET - bar.rect.height(),
|
||||
NavigationEdge::Top => stage.top() + ui_kit::NAV_BORDER + ui_kit::NAV_STAGE_INSET,
|
||||
NavigationEdge::Bottom => stage.bottom() - ui_kit::NAV_BORDER - ui_kit::NAV_STAGE_INSET - bar.rect.height(),
|
||||
};
|
||||
let delta = pos2(x, y) - bar.rect.min;
|
||||
bar.rect = bar.rect.translate(delta);
|
||||
@@ -249,7 +249,7 @@ fn activate(app: &mut SolveApp, slot: &Slot) {
|
||||
_ => {
|
||||
if let Some(command) = slot.command {
|
||||
let params = match slot.kind {
|
||||
Kind::Tool if command == "view.tool" => json!({"tool": if selected(app, slot.id) { "none" } else { slot.id }}),
|
||||
NavigationKind::Tool if command == "view.tool" => json!({"tool": if selected(app, slot.id) { "none" } else { slot.id }}),
|
||||
_ if slot.id == "ambient_occlusion" => json!({"enabled": !app.preferences.ambient_occlusion}),
|
||||
_ => json!({}),
|
||||
};
|
||||
@@ -259,7 +259,7 @@ fn activate(app: &mut SolveApp, slot: &Slot) {
|
||||
}
|
||||
}
|
||||
|
||||
fn tooltip(app: &SolveApp, response: egui::Response, slot: &Slot, hidden: Option<&str>, along: Rect, edge: Edge) -> egui::Response {
|
||||
fn tooltip(app: &SolveApp, response: egui::Response, slot: &Slot, hidden: Option<&str>, along: Rect, edge: NavigationEdge) -> egui::Response {
|
||||
let key = slot.command.and_then(|id| crate::keymap::display_for(app, id));
|
||||
let label = if let Some(hidden) = hidden {
|
||||
app.tr(hidden).to_owned()
|
||||
@@ -299,7 +299,7 @@ pub(crate) fn show(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
|| !open.is_empty();
|
||||
let opacity = ui_kit::navigation_opacity(app.preferences.navigation_bar.idle, active);
|
||||
let background = ui.interact(bar.rect, ui.scope_id().with("navigation_bar_context"), egui::Sense::click());
|
||||
let opacity = if app.preferences.navigation_bar.idle == Idle::Hide {
|
||||
let opacity = if app.preferences.navigation_bar.idle == NavigationIdle::Hide {
|
||||
ui.ctx().animate_value_with_time(menu_id.with("opacity"), opacity, if active { ui_kit::NAV_FADE_SECONDS } else { 0.0 })
|
||||
} else {
|
||||
opacity
|
||||
@@ -311,8 +311,8 @@ pub(crate) fn show(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let bounds = response.rect;
|
||||
crate::scenario::publish_target(&format!("nav:slot:{}", slot.id), bounds);
|
||||
context_menu(app, &response, Some(index));
|
||||
if response.clicked() && slot.kind != Kind::Separator {
|
||||
if slot.kind == Kind::Menu {
|
||||
if response.clicked() && slot.kind != NavigationKind::Separator {
|
||||
if slot.kind == NavigationKind::Menu {
|
||||
open = if open == slot.id { String::new() } else { slot.id.to_owned() };
|
||||
} else {
|
||||
activate(app, slot);
|
||||
@@ -331,7 +331,7 @@ pub(crate) fn show(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
bar.more,
|
||||
ui_kit::NavigationButton {
|
||||
id: "more",
|
||||
kind: Kind::Menu,
|
||||
kind: NavigationKind::Menu,
|
||||
icon: "dots",
|
||||
label: app.tr("navbar.more"),
|
||||
selected: open == "more" || bar.overflow.iter().any(|slot| slot.id == open),
|
||||
@@ -353,8 +353,8 @@ pub(crate) fn show(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let ctx = ui.ctx().clone();
|
||||
// Menus open into the view: up from a bar at the bottom, down from one at the top.
|
||||
let (pivot, at) = match app.preferences.navigation_bar.edge {
|
||||
Edge::Bottom => (Align2::RIGHT_BOTTOM, anchor.right_top() - vec2(0.0, ui_kit::NAV_POPUP_GAP)),
|
||||
Edge::Top => (Align2::RIGHT_TOP, anchor.right_bottom() + vec2(0.0, ui_kit::NAV_POPUP_GAP)),
|
||||
NavigationEdge::Bottom => (Align2::RIGHT_BOTTOM, anchor.right_top() - vec2(0.0, ui_kit::NAV_POPUP_GAP)),
|
||||
NavigationEdge::Top => (Align2::RIGHT_TOP, anchor.right_bottom() + vec2(0.0, ui_kit::NAV_POPUP_GAP)),
|
||||
};
|
||||
let menu = Container::new(&ctx, Surface::Popup).area(menu_id.with("popup")).order(egui::Order::Foreground).pivot(pivot).fixed_pos(at).show(
|
||||
&ctx,
|
||||
@@ -426,7 +426,7 @@ pub(crate) fn paint_row(
|
||||
} else if editing.is_none() && slot.id == "projection" && projection_locked(app) {
|
||||
ui.set_opacity(ui.visuals().disabled_alpha);
|
||||
}
|
||||
if slot.kind == Kind::Separator {
|
||||
if slot.kind == NavigationKind::Separator {
|
||||
ui_kit::navigation_separator(ui, *bounds);
|
||||
ui.interact(
|
||||
*bounds,
|
||||
@@ -440,14 +440,14 @@ pub(crate) fn paint_row(
|
||||
icon: slot.current_icon(app.ui.visual_style),
|
||||
label: slot_text(app, slot),
|
||||
selected: if editing.is_some() { false } else { selected(app, slot.id) || open == slot.id },
|
||||
labels: if slot.kind == Kind::Menu {
|
||||
labels: if slot.kind == NavigationKind::Menu {
|
||||
bar.values
|
||||
&& slot.id == "visual_style"
|
||||
&& app.preferences.navigation_bar.menu_display(slot.id) == MenuDisplay::IconAndValue
|
||||
} else {
|
||||
bar.labels
|
||||
},
|
||||
menu: slot.kind == Kind::Menu,
|
||||
menu: slot.kind == NavigationKind::Menu,
|
||||
};
|
||||
if editing.is_some() {
|
||||
ui_kit::navigation_button_editable(ui, *bounds, button, true)
|
||||
@@ -472,7 +472,7 @@ pub(crate) fn paint_row(
|
||||
});
|
||||
}
|
||||
// The settings' preview keeps its tips above, wherever the bar sits in the view.
|
||||
let edge = if editing.is_some() { Edge::Bottom } else { app.preferences.navigation_bar.edge };
|
||||
let edge = if editing.is_some() { NavigationEdge::Bottom } else { app.preferences.navigation_bar.edge };
|
||||
responses.push((index, *slot, tooltip(app, response, slot, hidden, bar.rect, edge)));
|
||||
}
|
||||
responses
|
||||
@@ -534,7 +534,7 @@ fn style_menu(app: &mut SolveApp, ui: &mut egui::Ui) -> bool {
|
||||
|
||||
fn overflow_menu(app: &mut SolveApp, ui: &mut egui::Ui, slots: &[&Slot], open: &mut String) {
|
||||
for slot in slots {
|
||||
if slot.kind == Kind::Separator {
|
||||
if slot.kind == NavigationKind::Separator {
|
||||
let start = ui.next_widget_position();
|
||||
ui_kit::navigation_menu_separator(ui);
|
||||
let bounds = Rect::from_min_max(start, pos2(start.x + ui.available_width(), ui.next_widget_position().y));
|
||||
@@ -542,7 +542,7 @@ fn overflow_menu(app: &mut SolveApp, ui: &mut egui::Ui, slots: &[&Slot], open: &
|
||||
continue;
|
||||
}
|
||||
let mut item = ui_kit::NavigationMenuRow::new(slot.id, app.tr(slot.label), slot.current_icon(app.ui.visual_style));
|
||||
if matches!(slot.kind, Kind::Tool | Kind::Toggle) {
|
||||
if matches!(slot.kind, NavigationKind::Tool | NavigationKind::Toggle) {
|
||||
item.selected = selected(app, slot.id);
|
||||
}
|
||||
let response = item.show(ui);
|
||||
@@ -552,8 +552,8 @@ fn overflow_menu(app: &mut SolveApp, ui: &mut egui::Ui, slots: &[&Slot], open: &
|
||||
if slot.id == "display" {
|
||||
crate::scenario::publish_handle("nav:view", response.rect.center());
|
||||
}
|
||||
if tooltip(app, response.clone(), slot, None, response.rect, Edge::Bottom).clicked() {
|
||||
if slot.kind == Kind::Menu {
|
||||
if tooltip(app, response.clone(), slot, None, response.rect, NavigationEdge::Bottom).clicked() {
|
||||
if slot.kind == NavigationKind::Menu {
|
||||
*open = slot.id.to_owned();
|
||||
} else {
|
||||
activate(app, slot);
|
||||
|
||||
@@ -975,7 +975,7 @@ impl crate::SolveApp {
|
||||
self.shell.history_dock_space = None;
|
||||
self.shell.history_folding = None;
|
||||
self.shell.history_fold_motion = None;
|
||||
crate::theme::TabAccent::publish(self, ui.ctx());
|
||||
self.publish_tab_accent(ui.ctx());
|
||||
crate::browser::publish_row_prefs(self, ui.ctx());
|
||||
let mut placement = render_domain;
|
||||
self.shell.status_bar = if crate::status_bar::shown(self) {
|
||||
|
||||
@@ -1024,6 +1024,32 @@ pub fn complete(ui: &mut egui::Ui, resp: &egui::Response, text: &mut String) ->
|
||||
crate::expression_complete::complete(ui, resp, text) || crate::value_step::step(ui, resp, text)
|
||||
}
|
||||
|
||||
/// SolidCraft's expression language for the kit's value fields: literals and units as
|
||||
/// `doc::expr` reads them, completion and stepping by [`complete`].
|
||||
pub(crate) struct DocExpressions;
|
||||
|
||||
impl ui_kit::Expressions for DocExpressions {
|
||||
fn is_literal(&self, text: &str) -> bool {
|
||||
solidcraft_engine::doc::expr::is_literal(text)
|
||||
}
|
||||
|
||||
fn is_unit(&self, unit: &str) -> bool {
|
||||
solidcraft_engine::doc::expr::unit_info(unit).is_some()
|
||||
}
|
||||
|
||||
fn complete(&self, ui: &mut egui::Ui, response: &egui::Response, text: &mut String) -> bool {
|
||||
complete(ui, response, text)
|
||||
}
|
||||
}
|
||||
|
||||
/// Give the kit's value fields in `ctx` SolidCraft's expressions, unless they have them already.
|
||||
/// The app calls this at the start of each frame entry point, so every context it draws into has them.
|
||||
pub(crate) fn install_expressions(ctx: &egui::Context) {
|
||||
if ui_kit::expressions(ctx).is_none() {
|
||||
ui_kit::install_expressions(ctx, Arc::new(DocExpressions));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -157,7 +157,7 @@ fn page_in_dir(app: &mut SolveApp, ui: &mut egui::Ui, dir: Option<&Path>) {
|
||||
};
|
||||
let t = crate::theme::Tokens::get();
|
||||
let path = dir.display().to_string();
|
||||
let searching = crate::prefs_search::querying(ui);
|
||||
let searching = crate::ui_kit::settings_search::querying(ui);
|
||||
// The page's own box is for browsing Plugins. The dialog search is the filter while it is active.
|
||||
if !searching {
|
||||
crate::ui_kit::search_field(
|
||||
@@ -176,26 +176,26 @@ fn page_in_dir(app: &mut SolveApp, ui: &mut egui::Ui, dir: Option<&Path>) {
|
||||
crate::i18n::tr("prefs.plugins_installed"),
|
||||
Some(&plugins.len().to_string()),
|
||||
|ui| {
|
||||
if plugins.is_empty() && (!searching || crate::prefs_search::shows_all(ui)) {
|
||||
if plugins.is_empty() && (!searching || crate::ui_kit::settings_search::shows_all(ui)) {
|
||||
crate::ui_kit::text(ui, crate::i18n::tr("ui.plugins_empty"), crate::ui_kit::Text::Hint("plugins_empty"));
|
||||
}
|
||||
ui.spacing_mut().item_spacing.y = t.space_8;
|
||||
let mut shown = 0usize;
|
||||
for plugin in plugins.iter().filter(|plugin| searching || matches_query(plugin, &query)) {
|
||||
if searching
|
||||
&& !crate::prefs_search::shows_all(ui)
|
||||
&& !crate::prefs_search::texts_hit(ui, &[&plugin.name, &plugin.id, &plugin.description, &plugin.version])
|
||||
&& !crate::ui_kit::settings_search::shows_all(ui)
|
||||
&& !crate::ui_kit::settings_search::texts_hit(ui, &[&plugin.name, &plugin.id, &plugin.description, &plugin.version])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
shown += 1;
|
||||
if searching {
|
||||
crate::prefs_search::reveal(ui);
|
||||
crate::prefs_search::push_force(ui);
|
||||
crate::ui_kit::settings_search::reveal(ui);
|
||||
crate::ui_kit::settings_search::push_force(ui);
|
||||
}
|
||||
plugin_card(app, ui, dir, plugin);
|
||||
if searching {
|
||||
crate::prefs_search::pop_force(ui);
|
||||
crate::ui_kit::settings_search::pop_force(ui);
|
||||
}
|
||||
}
|
||||
if !searching && !plugins.is_empty() && shown == 0 {
|
||||
@@ -257,11 +257,11 @@ fn plugin_card(app: &mut SolveApp, ui: &mut egui::Ui, dir: &Path, plugin: &Plugi
|
||||
}
|
||||
crate::scenario::publish_rect(&format!("prefs:plugin:{}:card", plugin.id), response.rect);
|
||||
let texts = [&plugin.name, &plugin.id, &plugin.description, &plugin.version];
|
||||
if let Some(text) = texts.iter().find(|text| crate::prefs_search::texts_hit(ui, &[*text])) {
|
||||
crate::prefs_search::remember(ui, text, response.rect);
|
||||
if let Some(text) = texts.iter().find(|text| crate::ui_kit::settings_search::texts_hit(ui, &[*text])) {
|
||||
crate::ui_kit::settings_search::remember(ui, text, response.rect);
|
||||
}
|
||||
for text in texts {
|
||||
crate::prefs_search::follow(ui, text, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, text, response.rect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,17 +458,17 @@ mod tests {
|
||||
crate::scenario::clear_handles();
|
||||
let mut output = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| {
|
||||
ui.set_width(646.0);
|
||||
let _search = crate::prefs_search::begin(ui, query);
|
||||
crate::prefs_search::begin_page(ui, crate::prefs::PAGE_PLUGINS, "Extend > Plugins");
|
||||
let _search = crate::ui_kit::settings_search::begin(ui, query);
|
||||
crate::ui_kit::settings_search::begin_page(ui, crate::prefs::PAGE_PLUGINS, "Extend > Plugins");
|
||||
crate::ui_kit::scope(ui, |ui| page_in_dir(&mut h.app, ui, Some(&dir)));
|
||||
crate::prefs_search::store_nav(ui);
|
||||
crate::ui_kit::settings_search::store_nav(ui);
|
||||
});
|
||||
output.textures_delta.clear();
|
||||
}
|
||||
let (_, _, hits) = crate::prefs_search::nav(&ctx);
|
||||
let (_, _, hits) = crate::ui_kit::settings_search::nav(&ctx);
|
||||
assert_eq!(hits.len(), 1, "{query}: one real plugin result");
|
||||
assert_eq!(hits[0].page, crate::prefs::PAGE_PLUGINS);
|
||||
crate::prefs_search::set_reveal(&ctx, Some(crate::prefs_search::reveal_for(hits[0].page, &hits[0].label)));
|
||||
crate::ui_kit::settings_search::set_reveal(&ctx, Some(crate::ui_kit::settings_search::reveal_for(hits[0].page, &hits[0].label)));
|
||||
page_frame(&mut h.app, &ctx, &dir, vec![]);
|
||||
let card = crate::scenario::rect_of("prefs:plugin:public-one:card").unwrap();
|
||||
assert!(crate::scenario::handle_at("prefs:revealed").unwrap().distance(card.center()) <= 1.0);
|
||||
|
||||
+57
-64
@@ -467,11 +467,11 @@ fn search_header(ui: &mut egui::Ui, app: &mut SolveApp) {
|
||||
}
|
||||
crate::scenario::publish_handle("prefs:search", field.rect.center());
|
||||
if field.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
crate::ui_kit::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
settle_search(app);
|
||||
crate::prefs_search::set_reveal(ui.ctx(), app.prefs_window.reveal.clone());
|
||||
crate::prefs_search::set_force_scroll(ui.ctx(), !app.prefs_window.filter.trim().is_empty());
|
||||
crate::ui_kit::settings_search::set_reveal(ui.ctx(), app.prefs_window.reveal.clone());
|
||||
crate::ui_kit::settings_search::set_force_scroll(ui.ctx(), !app.prefs_window.filter.trim().is_empty());
|
||||
}
|
||||
|
||||
/// Keys for the search field. Shortcut capture keeps the key for itself.
|
||||
@@ -484,7 +484,7 @@ fn drive_search(ui: &mut egui::Ui, app: &mut SolveApp) -> bool {
|
||||
if ctx.input_mut(|input| input.consume_key(egui::Modifiers::COMMAND, egui::Key::F)) {
|
||||
ctx.memory_mut(|memory| memory.request_focus(id));
|
||||
}
|
||||
let sink = crate::prefs_search::key_sink(&ctx);
|
||||
let sink = crate::ui_kit::settings_search::key_sink(&ctx);
|
||||
let mut typed = false;
|
||||
if !sink {
|
||||
let modifiers = ctx.input(|input| input.modifiers);
|
||||
@@ -511,20 +511,20 @@ fn drive_search(ui: &mut egui::Ui, app: &mut SolveApp) -> bool {
|
||||
if query.is_empty() {
|
||||
return typed;
|
||||
}
|
||||
let (nav_query, cursor, hits) = crate::prefs_search::nav(&ctx);
|
||||
let (nav_query, cursor, hits) = crate::ui_kit::settings_search::nav(&ctx);
|
||||
let fresh = nav_query == query;
|
||||
let cursor = if fresh { cursor.min(hits.len().saturating_sub(1)) } else { 0 };
|
||||
if ctx.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowDown)) && fresh && !hits.is_empty() {
|
||||
crate::prefs_search::set_cursor(&ctx, (cursor + 1).min(hits.len() - 1));
|
||||
crate::ui_kit::settings_search::set_cursor(&ctx, (cursor + 1).min(hits.len() - 1));
|
||||
} else if ctx.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowUp)) && fresh && !hits.is_empty() {
|
||||
crate::prefs_search::set_cursor(&ctx, cursor.saturating_sub(1));
|
||||
crate::ui_kit::settings_search::set_cursor(&ctx, cursor.saturating_sub(1));
|
||||
} else if fresh && !hits.is_empty() && ctx.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Enter)) {
|
||||
if let Some(hit) = hits.get(cursor) {
|
||||
open_result(app, hit.page, &hit.label);
|
||||
ctx.memory_mut(|memory| memory.surrender_focus(id));
|
||||
}
|
||||
} else {
|
||||
crate::prefs_search::set_cursor(&ctx, cursor);
|
||||
crate::ui_kit::settings_search::set_cursor(&ctx, cursor);
|
||||
}
|
||||
if !app.prefs_window.filter.trim().is_empty() && ctx.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
|
||||
app.prefs_window.filter.clear();
|
||||
@@ -551,7 +551,7 @@ fn open_result(app: &mut SolveApp, page: usize, label: &str) {
|
||||
app.prefs_window.search_from = None;
|
||||
app.prefs_window.section = page;
|
||||
app.prefs_window.land_on = Some(page);
|
||||
app.prefs_window.reveal = Some(crate::prefs_search::reveal_for(page, label));
|
||||
app.prefs_window.reveal = Some(crate::ui_kit::settings_search::reveal_for(page, label));
|
||||
}
|
||||
|
||||
/// An empty field returns to the page that was up when the search started.
|
||||
@@ -1802,7 +1802,7 @@ pub struct PrefsWindow {
|
||||
/// Set when a result is chosen, so the page draw does not put the previous tab back.
|
||||
land_on: Option<usize>,
|
||||
/// The row to scroll to after a result is chosen.
|
||||
reveal: Option<crate::prefs_search::Reveal>,
|
||||
reveal: Option<crate::ui_kit::settings_search::Reveal>,
|
||||
/// Where the theme Import tile was drawn last frame: a file dropped there is a theme (#8226).
|
||||
import_tile: Option<egui::Rect>,
|
||||
}
|
||||
@@ -2525,16 +2525,6 @@ pub(crate) fn pivot_under(app: &SolveApp, proj: &crate::viewport::Proj, at: Pos2
|
||||
if b.is_empty() { app.cam.target } else { b.center() }
|
||||
}
|
||||
|
||||
/// A plain number typed into a field, with a decimal point or a decimal comma (#1347): "2.5"
|
||||
/// and "2,5" are both 2.5, as a numeric keypad types either depending on the locale. Spaces are
|
||||
/// ignored and "−" is a minus, as in egui's own parser. A comma beside a point ("1.234,5") or a
|
||||
/// second comma is not guessed at: the text is not a number.
|
||||
pub fn parse_number(text: &str) -> Option<f64> {
|
||||
let t: String = text.chars().filter(|c| !c.is_whitespace()).map(|c| if c == '\u{2212}' { '-' } else { c }).collect();
|
||||
let t = if !t.contains('.') && t.matches(',').count() == 1 { t.replacen(',', ".", 1) } else { t };
|
||||
t.parse::<f64>().ok().filter(|v| v.is_finite())
|
||||
}
|
||||
|
||||
// Existing callers retain their numeric-editor API; kit owns the widget.
|
||||
|
||||
/// A length typed into a field, in millimetres: a bare number is in the design's units (`scale`
|
||||
@@ -2712,7 +2702,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
app.prefs_window.search_from = None;
|
||||
app.prefs_window.land_on = None;
|
||||
app.prefs_window.reveal = None;
|
||||
crate::prefs_search::set_reveal(ctx, None);
|
||||
crate::ui_kit::settings_search::set_reveal(ctx, None);
|
||||
app.prefs_window.snapshot = None;
|
||||
// A question left up when the dialog closed is not still asked when it opens again.
|
||||
app.space_mouse.restore_pending = false;
|
||||
@@ -2730,8 +2720,8 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
{
|
||||
app.prefs_window.reveal = None;
|
||||
}
|
||||
let sink = crate::prefs_search::shift_key_sink(ctx);
|
||||
crate::prefs_search::set_key_sink_prev(ctx, sink);
|
||||
let sink = crate::ui_kit::settings_search::shift_key_sink(ctx);
|
||||
crate::ui_kit::settings_search::set_key_sink_prev(ctx, sink);
|
||||
let mut button: Option<&str> = None;
|
||||
let before = app.preferences.clone();
|
||||
let capturing = crate::keymap::capturing(app);
|
||||
@@ -2904,38 +2894,38 @@ fn panel_restore_banner(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
|
||||
fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
panel_restore_banner(app, ui, t);
|
||||
let _search = crate::prefs_search::begin(ui, &app.prefs_window.filter);
|
||||
if crate::prefs_search::querying(ui) {
|
||||
let _search = crate::ui_kit::settings_search::begin(ui, &app.prefs_window.filter);
|
||||
if crate::ui_kit::settings_search::querying(ui) {
|
||||
// Paint only the pages that match. A first pass is invisible, so the 3D mouse rows and the
|
||||
// agent list cannot draw on top of the setting that did match.
|
||||
let mut keep = vec![false; WINDOW_PAGES.len()];
|
||||
for (index, _) in WINDOW_PAGES.iter().enumerate() {
|
||||
crate::prefs_search::begin_page(ui, index, &page_path(index));
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let mut probe = crate::prefs_search::detached_child(ui, ("prefs-search-probe", index), true);
|
||||
crate::ui_kit::settings_search::begin_page(ui, index, &page_path(index));
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
let mut probe = crate::ui_kit::settings_search::detached_child(ui, ("prefs-search-probe", index), true);
|
||||
draw_page(app, &mut probe, index, t);
|
||||
keep[index] = crate::prefs_search::hits(ui) > before;
|
||||
keep[index] = crate::ui_kit::settings_search::hits(ui) > before;
|
||||
}
|
||||
crate::prefs_search::rewind_search_layout(ui);
|
||||
crate::ui_kit::settings_search::rewind_search_layout(ui);
|
||||
for (index, _) in WINDOW_PAGES.iter().enumerate() {
|
||||
if !keep.get(index).copied().unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
crate::prefs_search::begin_page(ui, index, &page_path(index));
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let mut child = crate::prefs_search::detached_child(ui, ("prefs-search-page", index), false);
|
||||
crate::ui_kit::settings_search::begin_page(ui, index, &page_path(index));
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
let mut child = crate::ui_kit::settings_search::detached_child(ui, ("prefs-search-page", index), false);
|
||||
draw_page(app, &mut child, index, t);
|
||||
crate::prefs_search::commit_search_child(ui, &child, before);
|
||||
crate::ui_kit::settings_search::commit_search_child(ui, &child, before);
|
||||
}
|
||||
if crate::prefs_search::hits(ui) == 0 {
|
||||
if crate::ui_kit::settings_search::hits(ui) == 0 {
|
||||
ui.label(egui::RichText::new(crate::i18n::tr("prefs.search_empty")).size(t.font_body).color(t.form_dim));
|
||||
}
|
||||
} else {
|
||||
let label = page_path(app.prefs_window.section);
|
||||
crate::prefs_search::begin_page(ui, app.prefs_window.section, &label);
|
||||
crate::ui_kit::settings_search::begin_page(ui, app.prefs_window.section, &label);
|
||||
draw_page(app, ui, app.prefs_window.section, t);
|
||||
}
|
||||
crate::prefs_search::store_nav(ui);
|
||||
crate::ui_kit::settings_search::store_nav(ui);
|
||||
}
|
||||
|
||||
fn draw_page(app: &mut SolveApp, ui: &mut egui::Ui, index: usize, t: &Tokens) {
|
||||
@@ -4649,7 +4639,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn navigation_bar_preferences_round_trip_and_recover_hostile_fields() {
|
||||
use crate::navigation_bar::{Edge, Idle, Labels, NavigationBar, Position, Size};
|
||||
use crate::navigation_bar::{Labels, NavigationBar, Position};
|
||||
use crate::ui_kit::{NavigationEdge, NavigationIdle, NavigationSize};
|
||||
use serde_json::json;
|
||||
assert_eq!(super::lenient(&json!({})).navigation_bar, NavigationBar::default());
|
||||
let p = super::lenient(&json!({"navigation_bar": {
|
||||
@@ -4659,7 +4650,7 @@ mod tests {
|
||||
assert_eq!(p.navigation_bar.slots, ["pan", "future_slot", "separator", "separator", "separator", "separator", "fit"]);
|
||||
assert_eq!(
|
||||
(p.navigation_bar.position, p.navigation_bar.edge, p.navigation_bar.size, p.navigation_bar.labels, p.navigation_bar.idle),
|
||||
(Position::Left, Edge::Top, Size::Regular, Labels::Labels, Idle::Hide)
|
||||
(Position::Left, NavigationEdge::Top, NavigationSize::Regular, Labels::Labels, NavigationIdle::Hide)
|
||||
);
|
||||
let saved = serde_json::to_value(&p).unwrap();
|
||||
assert_eq!(serde_json::from_value::<super::Prefs>(saved).unwrap(), p);
|
||||
@@ -4669,10 +4660,10 @@ mod tests {
|
||||
let mut expected = p.clone();
|
||||
match field {
|
||||
"position" => expected.navigation_bar.position = Position::default(),
|
||||
"edge" => expected.navigation_bar.edge = Edge::default(),
|
||||
"size" => expected.navigation_bar.size = Size::default(),
|
||||
"edge" => expected.navigation_bar.edge = NavigationEdge::default(),
|
||||
"size" => expected.navigation_bar.size = NavigationSize::default(),
|
||||
"labels" => expected.navigation_bar.labels = Labels::default(),
|
||||
_ => expected.navigation_bar.idle = Idle::default(),
|
||||
_ => expected.navigation_bar.idle = NavigationIdle::default(),
|
||||
}
|
||||
assert_eq!(super::lenient(&value), expected, "{field}");
|
||||
value["navigation_bar"][field] = json!("unknown");
|
||||
@@ -4736,7 +4727,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn navigation_bar_editor_buttons_and_appearance_update_preferences() {
|
||||
use crate::navigation_bar::{Idle, Labels, MenuDisplay, Position, Size};
|
||||
use crate::navigation_bar::{Labels, MenuDisplay, Position};
|
||||
use crate::ui_kit::{NavigationIdle, NavigationSize};
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.call("ui.resize", json!({"width":1600,"height":1100}));
|
||||
@@ -4753,20 +4745,20 @@ mod tests {
|
||||
assert_eq!(h.app.preferences.navigation_bar.menu_display("visual_style"), MenuDisplay::IconAndValue);
|
||||
navbar_click(&mut h, "prefs:navbar:anchor:0");
|
||||
assert_eq!(h.app.preferences.navigation_bar.position, Position::Left);
|
||||
assert_eq!(h.app.preferences.navigation_bar.edge, crate::navigation_bar::Edge::Top);
|
||||
assert_eq!(h.app.preferences.navigation_bar.edge, crate::ui_kit::NavigationEdge::Top);
|
||||
navbar_click(&mut h, "prefs:navbar:anchor:5");
|
||||
assert_eq!(h.app.preferences.navigation_bar.position, Position::Right);
|
||||
assert_eq!(h.app.preferences.navigation_bar.edge, crate::navigation_bar::Edge::Bottom);
|
||||
assert_eq!(h.app.preferences.navigation_bar.edge, crate::ui_kit::NavigationEdge::Bottom);
|
||||
navbar_click(&mut h, "prefs:navbar:size:0");
|
||||
assert_eq!(h.app.preferences.navigation_bar.size, Size::Compact);
|
||||
assert_eq!(h.app.preferences.navigation_bar.size, NavigationSize::Compact);
|
||||
navbar_click(&mut h, "prefs:navbar:size:1");
|
||||
assert_eq!(h.app.preferences.navigation_bar.size, Size::Regular);
|
||||
assert_eq!(h.app.preferences.navigation_bar.size, NavigationSize::Regular);
|
||||
navbar_click(&mut h, "prefs:navbar:buttons:1");
|
||||
assert_eq!(h.app.preferences.navigation_bar.labels, Labels::Labels);
|
||||
navbar_click(&mut h, "prefs:navbar:idle:0");
|
||||
assert_eq!(h.app.preferences.navigation_bar.idle, Idle::Always);
|
||||
assert_eq!(h.app.preferences.navigation_bar.idle, NavigationIdle::Always);
|
||||
navbar_click(&mut h, "prefs:navbar:idle:2");
|
||||
assert_eq!(h.app.preferences.navigation_bar.idle, Idle::Hide);
|
||||
assert_eq!(h.app.preferences.navigation_bar.idle, NavigationIdle::Hide);
|
||||
let before = h.app.preferences.navigation_bar.clone();
|
||||
navbar_click(&mut h, "prefs:navbar:preview_as:1");
|
||||
assert_eq!(h.app.preferences.navigation_bar, before, "canvas preview is transient");
|
||||
@@ -4982,18 +4974,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn navigation_bar_preview_stage_follows_anchor_live() {
|
||||
use crate::navigation_bar::{Edge, Position};
|
||||
use crate::navigation_bar::Position;
|
||||
use crate::ui_kit::NavigationEdge;
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.call("ui.resize", json!({"width":1600,"height":1100}));
|
||||
h.call("ui.prefs", json!({"open":"Interface/Navigation bar","navigation_bar":{"slots":["pan","fit","display"]}}));
|
||||
for (index, edge, position) in [
|
||||
(0, Edge::Top, Position::Left),
|
||||
(1, Edge::Top, Position::Centre),
|
||||
(2, Edge::Top, Position::Right),
|
||||
(3, Edge::Bottom, Position::Left),
|
||||
(4, Edge::Bottom, Position::Centre),
|
||||
(5, Edge::Bottom, Position::Right),
|
||||
(0, NavigationEdge::Top, Position::Left),
|
||||
(1, NavigationEdge::Top, Position::Centre),
|
||||
(2, NavigationEdge::Top, Position::Right),
|
||||
(3, NavigationEdge::Bottom, Position::Left),
|
||||
(4, NavigationEdge::Bottom, Position::Centre),
|
||||
(5, NavigationEdge::Bottom, Position::Right),
|
||||
] {
|
||||
navbar_click(&mut h, &format!("prefs:navbar:anchor:{index}"));
|
||||
h.frames(3);
|
||||
@@ -5004,11 +4997,11 @@ mod tests {
|
||||
assert_eq!(stage.height(), crate::ui_kit::NAV_STAGE_HEIGHT);
|
||||
let inset = crate::ui_kit::NAV_BORDER + crate::ui_kit::NAV_STAGE_INSET;
|
||||
match edge {
|
||||
Edge::Top => {
|
||||
NavigationEdge::Top => {
|
||||
assert_eq!(bar.top(), stage.top() + inset);
|
||||
assert!(count.top() >= bar.bottom(), "the count sits below a top bar");
|
||||
}
|
||||
Edge::Bottom => {
|
||||
NavigationEdge::Bottom => {
|
||||
assert_eq!(bar.bottom(), stage.bottom() - inset);
|
||||
assert!(count.bottom() <= bar.top(), "the count sits above a bottom bar");
|
||||
}
|
||||
@@ -8643,13 +8636,13 @@ mod tests {
|
||||
#[test]
|
||||
fn number_fields_take_a_decimal_point_or_a_decimal_comma() {
|
||||
// #1347
|
||||
assert_eq!(super::parse_number("2.5"), Some(2.5));
|
||||
assert_eq!(super::parse_number("2,5"), Some(2.5));
|
||||
assert_eq!(super::parse_number(" -0,25 "), Some(-0.25));
|
||||
assert_eq!(super::parse_number("\u{2212}1,5"), Some(-1.5));
|
||||
assert_eq!(super::parse_number("12"), Some(12.0));
|
||||
assert_eq!(crate::ui_kit::parse_number("2.5"), Some(2.5));
|
||||
assert_eq!(crate::ui_kit::parse_number("2,5"), Some(2.5));
|
||||
assert_eq!(crate::ui_kit::parse_number(" -0,25 "), Some(-0.25));
|
||||
assert_eq!(crate::ui_kit::parse_number("\u{2212}1,5"), Some(-1.5));
|
||||
assert_eq!(crate::ui_kit::parse_number("12"), Some(12.0));
|
||||
for bad in ["1.234,5", "1,2,3", ",", "abc", "", "inf", "NaN"] {
|
||||
assert_eq!(super::parse_number(bad), None, "{bad}");
|
||||
assert_eq!(crate::ui_kit::parse_number(bad), None, "{bad}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = setting(app, ui).backing_on_hover_text(app.tr(help));
|
||||
crate::scenario::publish_target(&format!("prefs:{target}"), response.rect);
|
||||
crate::scenario::publish_target(&format!("dialog:{legacy}"), response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr(help));
|
||||
}
|
||||
});
|
||||
@@ -141,7 +141,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let response = setting::offer_stand_up(app, ui).backing_on_hover_text(app.tr("prefs.stand_up_note"));
|
||||
crate::scenario::publish_target("prefs:offer_stand_up", response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr("prefs.stand_up_note"));
|
||||
}
|
||||
});
|
||||
@@ -179,7 +179,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = setting::double_click_components(app, ui).backing_on_hover_text(app.tr("prefs.design_tools.double_click_help"));
|
||||
crate::scenario::publish_target("prefs:double_click_components", response.rect);
|
||||
crate::scenario::publish_target("dialog:Double-click a component to activate it, or to edit a linked one in place", response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr("prefs.design_tools.double_click_help"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -434,7 +434,7 @@ fn paint_monitor(p: &egui::Painter, rect: egui::Rect, ink: egui::Color32) {
|
||||
/// shows the rows without it, like every other card.
|
||||
fn computer_card(ui: &mut egui::Ui, add: impl FnOnce(&mut egui::Ui)) {
|
||||
let t = Tokens::get();
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
add(ui);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ fn browser_visibility(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui_kit::settings_form(ui, 0.0, |ui| {
|
||||
ui_kit::row(ui, app.tr("prefs.browser_shown"), |ui| {
|
||||
ui.spacing_mut().item_spacing.x = Tokens::get().space_6;
|
||||
crate::prefs_search::note_current(ui, app.tr("prefs.layout_page.on_hover"));
|
||||
crate::ui_kit::settings_search::note_current(ui, app.tr("prefs.layout_page.on_hover"));
|
||||
ui.add_enabled_ui(!crate::timeline_left::stacked(app), |ui| {
|
||||
let mut selected = usize::from(app.preferences.browser_show == BrowserShow::Hover);
|
||||
if ui_kit::settings_segments(
|
||||
|
||||
@@ -163,7 +163,7 @@ fn orbit(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let width = ui_kit::ContentWidth::label(ui, label) + Tokens::get().space_16 + Tokens::get().space_4;
|
||||
ui_kit::settings_form(ui, width, |ui| {
|
||||
ui_kit::settings_row_help(ui, label, help, |ui| {
|
||||
crate::prefs_search::note_choices(ui, &orbits.each_ref().map(String::as_str));
|
||||
crate::ui_kit::settings_search::note_choices(ui, &orbits.each_ref().map(String::as_str));
|
||||
let mut selected = usize::from(!app.preferences.space_mouse_model_pivot);
|
||||
let response = ui.scope(|ui| {
|
||||
ui_kit::segmented(
|
||||
@@ -180,11 +180,11 @@ fn orbit(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if response.inner {
|
||||
app.preferences.space_mouse_model_pivot = selected == 0;
|
||||
}
|
||||
if !crate::prefs_search::texts_hit(ui, &[label]) {
|
||||
if !crate::ui_kit::settings_search::texts_hit(ui, &[label]) {
|
||||
for option in &orbits {
|
||||
if let Some(rect) = crate::scenario::rect_of(&format!("dialog:{label}:{option}")) {
|
||||
crate::prefs_search::remember(ui, option, rect);
|
||||
crate::prefs_search::follow(ui, option, rect);
|
||||
crate::ui_kit::settings_search::remember(ui, option, rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! Navigation bar editor. The shared viewport painter owns the preview's appearance.
|
||||
use crate::navigation::NavCaps;
|
||||
use crate::navigation_bar::{self, Edge, Idle, Kind, Labels, MenuDisplay, NavigationBar, Position, Size, SlotOptions};
|
||||
use crate::navigation_bar::{self, Labels, MenuDisplay, NavigationBar, Position, SlotOptions};
|
||||
use crate::navigation_bar_view;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{NavigationEdge, NavigationIdle, NavigationKind, NavigationSize};
|
||||
use crate::{SolveApp, scenario, theme::Tokens, ui_kit};
|
||||
use egui::{Rect, Sense, vec2};
|
||||
use serde_json::json;
|
||||
@@ -63,7 +64,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
});
|
||||
});
|
||||
scenario::publish_rect("prefs:navbar:header", header.response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
preview(app, ui, &mut state, width);
|
||||
if state.selected.is_some() {
|
||||
let card = ui_kit::settings_card(ui, |ui| inspector(app, ui, &mut state));
|
||||
@@ -240,8 +241,8 @@ fn slot_count(app: &SolveApp, ui: &mut egui::Ui, stage: Rect, bar: Rect) {
|
||||
Position::Right => stage.right() - ui_kit::NAV_BORDER - ui_kit::NAV_STAGE_INSET - ui_kit::NAV_PADDING - size.x,
|
||||
};
|
||||
let y = match app.preferences.navigation_bar.edge {
|
||||
Edge::Top => bar.bottom() + t.space_8,
|
||||
Edge::Bottom => bar.top() - t.space_8 - size.y,
|
||||
NavigationEdge::Top => bar.bottom() + t.space_8,
|
||||
NavigationEdge::Bottom => bar.top() - t.space_8 - size.y,
|
||||
};
|
||||
let rect = Rect::from_min_size(egui::pos2(x, y), size);
|
||||
ui.painter().with_clip_rect(stage).galley(rect.min, galley, view.text_dim);
|
||||
@@ -283,7 +284,7 @@ fn drop_on_preview(app: &mut SolveApp, ui: &mut egui::Ui, state: &mut State, tar
|
||||
selected: false,
|
||||
kind: slot.kind,
|
||||
labels: false,
|
||||
menu: slot.kind == Kind::Menu,
|
||||
menu: slot.kind == NavigationKind::Menu,
|
||||
},
|
||||
app.preferences.navigation_bar.size,
|
||||
);
|
||||
@@ -437,7 +438,7 @@ fn available(app: &mut SolveApp, ui: &mut egui::Ui, state: &mut State) {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(Tokens::get().space_8, Tokens::get().space_8);
|
||||
for slot in navigation_bar::CATALOGUE {
|
||||
if slot.kind == Kind::Separator || app.preferences.navigation_bar.slots.iter().any(|id| id == slot.id) {
|
||||
if slot.kind == NavigationKind::Separator || app.preferences.navigation_bar.slots.iter().any(|id| id == slot.id) {
|
||||
continue;
|
||||
}
|
||||
let dragging = egui::DragAndDrop::payload::<Drag>(ui.ctx()).is_some_and(|payload| payload.from_menu && payload.id.slot == slot.id);
|
||||
@@ -480,9 +481,13 @@ fn option_grid(bar: &mut NavigationBar, ui: &mut egui::Ui, first: usize) {
|
||||
ui.allocate_ui_with_layout(vec2(width.max(0.0), t.control_height), egui::Layout::top_down(egui::Align::Min), |ui| match index {
|
||||
0 => anchor_option(bar, ui),
|
||||
1 => {
|
||||
option(ui, "size", &mut usize::from(bar.size == Size::Regular), &["navbar.custom.compact", "navbar.custom.regular"], |i| {
|
||||
bar.size = if i == 1 { Size::Regular } else { Size::Compact }
|
||||
});
|
||||
option(
|
||||
ui,
|
||||
"size",
|
||||
&mut usize::from(bar.size == NavigationSize::Regular),
|
||||
&["navbar.custom.compact", "navbar.custom.regular"],
|
||||
|i| bar.size = if i == 1 { NavigationSize::Regular } else { NavigationSize::Compact },
|
||||
);
|
||||
}
|
||||
2 => {
|
||||
option(
|
||||
@@ -498,16 +503,16 @@ fn option_grid(bar: &mut NavigationBar, ui: &mut egui::Ui, first: usize) {
|
||||
ui,
|
||||
"idle",
|
||||
&mut match bar.idle {
|
||||
Idle::Always => 0,
|
||||
Idle::Fade => 1,
|
||||
Idle::Hide => 2,
|
||||
NavigationIdle::Always => 0,
|
||||
NavigationIdle::Fade => 1,
|
||||
NavigationIdle::Hide => 2,
|
||||
},
|
||||
&["navbar.custom.always", "navbar.custom.fade", "navbar.custom.hide_idle"],
|
||||
|i| {
|
||||
bar.idle = match i {
|
||||
0 => Idle::Always,
|
||||
2 => Idle::Hide,
|
||||
_ => Idle::Fade,
|
||||
0 => NavigationIdle::Always,
|
||||
2 => NavigationIdle::Hide,
|
||||
_ => NavigationIdle::Fade,
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -520,7 +525,7 @@ fn option_grid(bar: &mut NavigationBar, ui: &mut egui::Ui, first: usize) {
|
||||
}
|
||||
|
||||
fn anchor_option(bar: &mut NavigationBar, ui: &mut egui::Ui) {
|
||||
let selected = usize::from(bar.edge == Edge::Bottom) * 3
|
||||
let selected = usize::from(bar.edge == NavigationEdge::Bottom) * 3
|
||||
+ match bar.position {
|
||||
Position::Left => 0,
|
||||
Position::Centre => 1,
|
||||
@@ -536,7 +541,7 @@ fn anchor_option(bar: &mut NavigationBar, ui: &mut egui::Ui) {
|
||||
]
|
||||
.map(crate::i18n::tr);
|
||||
if let Some(index) = ui_kit::settings_anchor_field(ui, crate::i18n::tr("navbar.custom.position"), selected, &names) {
|
||||
bar.edge = if index >= 3 { Edge::Bottom } else { Edge::Top };
|
||||
bar.edge = if index >= 3 { NavigationEdge::Bottom } else { NavigationEdge::Top };
|
||||
bar.position = match index % 3 {
|
||||
0 => Position::Left,
|
||||
2 => Position::Right,
|
||||
|
||||
@@ -177,7 +177,7 @@ fn orbit(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
ui_kit::settings_form(ui, width, |ui| {
|
||||
ui_kit::settings_row_help(ui, label, help, |ui| {
|
||||
let options = [crate::i18n::tr("prefs.navigation.turntable"), crate::i18n::tr("prefs.navigation.free")];
|
||||
crate::prefs_search::note_choices(ui, &options);
|
||||
crate::ui_kit::settings_search::note_choices(ui, &options);
|
||||
let mut selected = usize::from(app.preferences.free_orbit);
|
||||
let response = ui.scope(|ui| {
|
||||
ui_kit::segmented(
|
||||
@@ -190,11 +190,11 @@ fn orbit(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
if response.inner {
|
||||
app.preferences.free_orbit = selected == 1;
|
||||
}
|
||||
if !crate::prefs_search::texts_hit(ui, &[label]) {
|
||||
if !crate::ui_kit::settings_search::texts_hit(ui, &[label]) {
|
||||
for option in options {
|
||||
if let Some(rect) = crate::scenario::rect_of(&format!("dialog:{label}:{option}")) {
|
||||
crate::prefs_search::remember(ui, option, rect);
|
||||
crate::prefs_search::follow(ui, option, rect);
|
||||
crate::ui_kit::settings_search::remember(ui, option, rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ fn workspace_selector(app: &mut SolveApp, ui: &mut Ui, view: &LayoutPreferencesV
|
||||
let title = app.tr("ui.panel_layout.layout_workspace").to_owned();
|
||||
ui_kit::settings_form(ui, 0.0, |ui| {
|
||||
ui_kit::sized_row(ui, &title, t.control_height, |ui| {
|
||||
crate::prefs_search::note_choices(ui, &options);
|
||||
crate::ui_kit::settings_search::note_choices(ui, &options);
|
||||
ui.horizontal(|ui| {
|
||||
let reset_label = app.tr("ui.panel_layout.reset_short");
|
||||
let reset_width = ui_kit::ContentWidth::name(ui, reset_label) + t.space_24 + t.space_16;
|
||||
@@ -239,8 +239,8 @@ fn tiles(app: &mut SolveApp, ui: &mut Ui, view: &LayoutPreferencesView, state: &
|
||||
Tile::Save => app.tr("ui.panel_layout.save_as_tile"),
|
||||
})
|
||||
.collect();
|
||||
crate::prefs_search::note_choices(ui, &names);
|
||||
crate::prefs_search::note_choices(
|
||||
crate::ui_kit::settings_search::note_choices(ui, &names);
|
||||
crate::ui_kit::settings_search::note_choices(
|
||||
ui,
|
||||
&[app.tr("ui.panel_layout.save"), app.tr("ui.panel_layout.replace"), app.tr("ui.panel_layout.reset")],
|
||||
);
|
||||
@@ -464,7 +464,7 @@ fn tile_menu(app: &SolveApp, ui: &mut Ui, state: &mut State, tile: &Response, en
|
||||
},
|
||||
);
|
||||
if popup.inner.is_some() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
crate::ui_kit::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,8 +535,8 @@ fn popover(app: &mut SolveApp, ui: &mut Ui, view: &LayoutPreferencesView, state:
|
||||
ui_kit::settings_form(ui, 0.0, |ui| {
|
||||
ui.set_width(width);
|
||||
ui.spacing_mut().item_spacing = vec2(t.space_8, t.space_8);
|
||||
crate::prefs_search::push_force(ui);
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
crate::ui_kit::settings_search::push_force(ui);
|
||||
crate::ui_kit::settings_search::note_key_sink(ui.ctx());
|
||||
match &mut popup.kind {
|
||||
PopoverKind::Save { name, chosen } => {
|
||||
let users: Vec<_> = view.entries.iter().filter(|entry| entry.user).collect();
|
||||
@@ -609,7 +609,7 @@ fn popover(app: &mut SolveApp, ui: &mut Ui, view: &LayoutPreferencesView, state:
|
||||
}
|
||||
}
|
||||
trap_focus(ui, &focus_ids);
|
||||
crate::prefs_search::pop_force(ui);
|
||||
crate::ui_kit::settings_search::pop_force(ui);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ fn snapping(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let controls_width = if available >= figure_width + t.space_14 + labels_width { available - figure_width - t.space_14 } else { available };
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::Min).with_main_wrap(true), |ui| {
|
||||
ui.spacing_mut().item_spacing.x = t.space_14;
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
let (rect, _) = ui.allocate_exact_size(egui::vec2(figure_width.min(available), t.space_24 * 2.0 + t.space_16), egui::Sense::hover());
|
||||
ui_kit::paint_sketch_guides(&ui.painter_at(rect), rect);
|
||||
}
|
||||
@@ -102,7 +102,7 @@ fn drawing(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
.map(|labels| labels.iter().map(|id| check_width(ui, app.tr(id), true)).fold(0.0, f32::max));
|
||||
let available = ui.available_width();
|
||||
let gap = t.space_24 + t.space_8;
|
||||
let two_columns = available >= t.space_24 * 20.0 + gap && !crate::prefs_search::suppress_chrome(ui);
|
||||
let two_columns = available >= t.space_24 * 20.0 + gap && !crate::ui_kit::settings_search::suppress_chrome(ui);
|
||||
let left = if two_columns { (available - gap) * natural[0] / (natural[0] + natural[1]).max(1.0) } else { available };
|
||||
let right = if two_columns { available - gap - left } else { available };
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::Min).with_main_wrap(true), |ui| {
|
||||
@@ -214,8 +214,8 @@ fn opening(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
);
|
||||
picture_group(ui, app.tr("prefs.sketch_exit_view"), |ui| {
|
||||
let label = app.tr("prefs.sketch_exit_view");
|
||||
let mut indexed = crate::prefs_search::texts_hit(ui, &[label]);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
let mut indexed = crate::ui_kit::settings_search::texts_hit(ui, &[label]);
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::section_heading(ui, label);
|
||||
}
|
||||
let cards = ui.horizontal_wrapped(|ui| {
|
||||
@@ -260,19 +260,19 @@ fn opening(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
crate::scenario::publish_target("dialog:When a sketch ends", response.rect);
|
||||
}
|
||||
let option = app.tr(label_id);
|
||||
if !indexed && crate::prefs_search::texts_hit(ui, &[option]) {
|
||||
crate::prefs_search::remember(ui, option, response.rect);
|
||||
if !indexed && crate::ui_kit::settings_search::texts_hit(ui, &[option]) {
|
||||
crate::ui_kit::settings_search::remember(ui, option, response.rect);
|
||||
indexed = true;
|
||||
}
|
||||
crate::prefs_search::follow(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, response.rect);
|
||||
if response.clicked() {
|
||||
app.preferences.sketch_exit_view = choice;
|
||||
}
|
||||
}
|
||||
});
|
||||
crate::scenario::publish_rect("prefs:sketch_exit_view:choices", cards.response.rect);
|
||||
crate::prefs_search::remember(ui, label, cards.response.rect);
|
||||
crate::prefs_search::follow(ui, label, cards.response.rect);
|
||||
crate::ui_kit::settings_search::remember(ui, label, cards.response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, label, cards.response.rect);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -342,8 +342,8 @@ fn selecting(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui_kit::settings_section(ui, "selection", app.tr("prefs.selecting"), |ui| {
|
||||
picture_group(ui, app.tr("prefs.sketch.click_selects"), |ui| {
|
||||
let label = app.tr("prefs.sketch.click_selects");
|
||||
let mut indexed = crate::prefs_search::texts_hit(ui, &[label]);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
let mut indexed = crate::ui_kit::settings_search::texts_hit(ui, &[label]);
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::section_heading(ui, label);
|
||||
}
|
||||
let cards = ui.horizontal_wrapped(|ui| {
|
||||
@@ -373,19 +373,19 @@ fn selecting(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
crate::scenario::publish_target("dialog:Click selects whole bodies", response.rect);
|
||||
}
|
||||
let option = app.tr(label);
|
||||
if !indexed && crate::prefs_search::texts_hit(ui, &[option]) {
|
||||
crate::prefs_search::remember(ui, option, response.rect);
|
||||
if !indexed && crate::ui_kit::settings_search::texts_hit(ui, &[option]) {
|
||||
crate::ui_kit::settings_search::remember(ui, option, response.rect);
|
||||
indexed = true;
|
||||
}
|
||||
crate::prefs_search::follow(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, response.rect);
|
||||
if response.clicked() {
|
||||
app.ui.pick_bodies = bodies;
|
||||
}
|
||||
}
|
||||
});
|
||||
crate::scenario::publish_rect("prefs:pick_bodies:choices", cards.response.rect);
|
||||
crate::prefs_search::remember(ui, label, cards.response.rect);
|
||||
crate::prefs_search::follow(ui, label, cards.response.rect);
|
||||
crate::ui_kit::settings_search::remember(ui, label, cards.response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, label, cards.response.rect);
|
||||
});
|
||||
check(
|
||||
ui,
|
||||
@@ -434,8 +434,8 @@ fn selecting(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
|
||||
/// Natural-height picture groups own their search entries without allocating a form label column.
|
||||
fn picture_group(ui: &mut egui::Ui, label: &str, add: impl FnOnce(&mut egui::Ui)) {
|
||||
let gate = crate::prefs_search::block_gate(ui, label, None);
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let gate = crate::ui_kit::settings_search::block_gate(ui, label, None);
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
let show = gate.as_ref().is_none_or(|gate| gate.show);
|
||||
let content = |ui: &mut egui::Ui| {
|
||||
let group = ui.scope(|ui| ui_kit::named_control(ui, label, add));
|
||||
@@ -444,10 +444,10 @@ fn picture_group(ui: &mut egui::Ui, label: &str, add: impl FnOnce(&mut egui::Ui)
|
||||
if show {
|
||||
content(ui);
|
||||
} else {
|
||||
crate::prefs_search::run_hidden(ui, label, content);
|
||||
crate::ui_kit::settings_search::run_hidden(ui, label, content);
|
||||
}
|
||||
if let Some(gate) = &gate {
|
||||
crate::prefs_search::block_finish(ui, gate, show, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, gate, show, before);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@ fn check(ui: &mut egui::Ui, key: &str, label: &str, help: Option<&str>, value: &
|
||||
let help = help.map(crate::i18n::tr);
|
||||
ui.horizontal_top(|ui| {
|
||||
let t = Tokens::get();
|
||||
let show_help = help.is_some() && !crate::prefs_search::suppress_chrome(ui);
|
||||
let show_help = help.is_some() && !crate::ui_kit::settings_search::suppress_chrome(ui);
|
||||
let width = (ui.available_width() - if show_help { t.space_8 + t.space_12 + 1.0 } else { 0.0 }).max(0.0).min(check_width(
|
||||
ui,
|
||||
crate::i18n::tr(label),
|
||||
|
||||
@@ -130,7 +130,7 @@ pub(super) fn choose(app: &mut SolveApp, night: bool, id: &str) {
|
||||
}
|
||||
|
||||
pub(super) fn row(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
row_contents(app, ui);
|
||||
return;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ pub(super) fn row(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
fn row_contents(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let key = ui.make_persistent_id("theme_picker_state");
|
||||
let mut state = ui.data(|d| d.get_temp::<State>(key)).unwrap_or_default();
|
||||
let search = crate::prefs_search::suppress_chrome(ui);
|
||||
let search = crate::ui_kit::settings_search::suppress_chrome(ui);
|
||||
let mut action = None;
|
||||
let mut action_theme = None;
|
||||
let t = Tokens::get();
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = setting(app, ui).backing_on_hover_text(app.tr(help));
|
||||
crate::scenario::publish_target(&format!("prefs:{key}"), response.rect);
|
||||
crate::scenario::publish_target(&format!("dialog:{legacy}"), response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr(help));
|
||||
}
|
||||
});
|
||||
@@ -42,7 +42,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let response = setting(app, ui).backing_on_hover_text(app.tr(help));
|
||||
crate::scenario::publish_target(&format!("prefs:{key}"), response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr(help));
|
||||
}
|
||||
});
|
||||
@@ -60,7 +60,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let response = setting::placement_handles(app, ui).backing_on_hover_text(app.tr("ui.placement_handles_tip"));
|
||||
crate::scenario::publish_target("prefs:placement_handles", response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr("ui.placement_handles_tip"));
|
||||
}
|
||||
});
|
||||
@@ -76,7 +76,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = setting::assembly_move_handles(app, ui).backing_on_hover_text(app.tr("joint_move.legacy_hint"));
|
||||
crate::scenario::publish_target("prefs:assembly_move_handles", response.rect);
|
||||
crate::scenario::publish_target("dialog:Use joint motion handles for assembly Move", response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, app.tr("joint_move.legacy_hint"));
|
||||
}
|
||||
});
|
||||
@@ -86,7 +86,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let tip = app.tr("prefs.move_drag_highlights_tip");
|
||||
let response = setting::move_drag_highlights(app, ui).backing_on_hover_text(tip);
|
||||
crate::scenario::publish_target("prefs:move_drag_highlights", response.rect);
|
||||
if !crate::prefs_search::suppress_chrome(ui) {
|
||||
if !crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
ui_kit::help_icon(ui, tip);
|
||||
}
|
||||
});
|
||||
@@ -162,12 +162,12 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
/// A single original figure beside a tool's complete setting stack. The columns wrap together
|
||||
/// with their translated labels; search uses the same block gate as other compound preferences.
|
||||
fn tool_group(ui: &mut egui::Ui, label: &str, figure: fn(&egui::Painter, egui::Rect), add: impl FnOnce(&mut egui::Ui)) {
|
||||
let gate = crate::prefs_search::block_gate(ui, label, None);
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let gate = crate::ui_kit::settings_search::block_gate(ui, label, None);
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
if gate.as_ref().is_some_and(|gate| !gate.show) {
|
||||
crate::prefs_search::run_hidden(ui, label, add);
|
||||
crate::ui_kit::settings_search::run_hidden(ui, label, add);
|
||||
if let Some(gate) = &gate {
|
||||
crate::prefs_search::block_finish(ui, gate, false, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, gate, false, before);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -190,14 +190,14 @@ fn tool_group(ui: &mut egui::Ui, label: &str, figure: fn(&egui::Painter, egui::R
|
||||
ui.allocate_ui_with_layout(egui::vec2(controls_width, 0.0), egui::Layout::top_down(egui::Align::Min), add);
|
||||
});
|
||||
if let Some(gate) = &gate {
|
||||
crate::prefs_search::block_finish(ui, gate, true, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, gate, true, before);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Create section's card: one border around a row per tool.
|
||||
fn create_card(ui: &mut egui::Ui, rows: impl FnOnce(&mut egui::Ui)) {
|
||||
let t = Tokens::get();
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if crate::ui_kit::settings_search::suppress_chrome(ui) {
|
||||
rows(ui);
|
||||
return;
|
||||
}
|
||||
@@ -215,17 +215,17 @@ fn create_card(ui: &mut egui::Ui, rows: impl FnOnce(&mut egui::Ui)) {
|
||||
/// One tool in the Create card: its icons in tiles and its name on the left, its settings on the
|
||||
/// right, a rule above every row but the first. Search uses the same block gate as `tool_group`.
|
||||
fn create_row(ui: &mut egui::Ui, label: &str, icons: &[&str], first: bool, add: impl FnOnce(&mut egui::Ui)) {
|
||||
let gate = crate::prefs_search::block_gate(ui, label, None);
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let gate = crate::ui_kit::settings_search::block_gate(ui, label, None);
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
if gate.as_ref().is_some_and(|gate| !gate.show) {
|
||||
crate::prefs_search::run_hidden(ui, label, add);
|
||||
crate::ui_kit::settings_search::run_hidden(ui, label, add);
|
||||
if let Some(gate) = &gate {
|
||||
crate::prefs_search::block_finish(ui, gate, false, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, gate, false, before);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let chrome = !crate::prefs_search::suppress_chrome(ui);
|
||||
let chrome = !crate::ui_kit::settings_search::suppress_chrome(ui);
|
||||
let margin = egui::Margin::symmetric(t.space_14 as i8, t.space_10 as i8);
|
||||
let row = egui::Frame::NONE.inner_margin(margin).show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
@@ -260,6 +260,6 @@ fn create_row(ui: &mut egui::Ui, label: &str, icons: &[&str], first: bool, add:
|
||||
ui.painter().hline(row.response.rect.x_range(), row.response.rect.top(), egui::Stroke::new(1.0, t.dialog_rule));
|
||||
}
|
||||
if let Some(gate) = &gate {
|
||||
crate::prefs_search::block_finish(ui, gate, true, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, gate, true, before);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,8 +395,9 @@ pub(crate) fn preview_scene(scene: &mut ProductScene, preview: Option<&Preview>)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn menu(app: &SolveApp, body: &str) -> crate::context_menu::Item {
|
||||
use crate::context_menu::{Item, cmd};
|
||||
pub(crate) fn menu(app: &SolveApp, body: &str) -> crate::ui_kit::Item {
|
||||
use crate::context_menu::cmd;
|
||||
use crate::ui_kit::Item;
|
||||
let selected_face = app
|
||||
.render_workspace
|
||||
.view
|
||||
|
||||
@@ -3,9 +3,9 @@ use super::{ImageMethod, RenderSelection, cpu, hdr, inspector, setups, studio};
|
||||
use crate::{
|
||||
SolveApp,
|
||||
browser::{self, Row},
|
||||
context_menu::{self, Item},
|
||||
context_menu,
|
||||
theme::Tokens,
|
||||
ui_kit,
|
||||
ui_kit::{self, Item},
|
||||
};
|
||||
use egui::Color32;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -519,7 +519,7 @@ fn stage_catalogs_fill_a_290_by_530_panel_and_keep_two_line_labels_inside_tiles(
|
||||
h.app.preferences.render_browser_side = "left".into();
|
||||
super::super::select_image_method(&mut h.app, method);
|
||||
library::open_category(&mut h.app, kind);
|
||||
crate::theme::TabAccent::publish(&h.app, &h.ctx);
|
||||
h.app.publish_tab_accent(&h.ctx);
|
||||
let document = h.app.session.doc.to_json();
|
||||
let history = (h.app.session.undo.len(), h.app.session.redo.len());
|
||||
// Panel::workspace reserves an 8 pt bottom gap in these bounds.
|
||||
|
||||
@@ -53,76 +53,17 @@ use solidcraft_engine::Session;
|
||||
use crate::control::{ControlRequest, Outcome, handle};
|
||||
use crate::{Services, SolveApp};
|
||||
|
||||
// The widget registry (handles, rects and counts drawn this frame) lives in its own crate so UI
|
||||
// components can publish without depending on the app; re-exported here for the scenario runner.
|
||||
pub use solidcraft_ui_registry::{
|
||||
clear_handles, count_of, handle_at, handles, publish_count, publish_handle, publish_rect, publish_target, publish_target_as, rect_of, rects,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
/// Drag handles drawn this frame (name, screen point): manipulators publish theirs so
|
||||
/// scenarios (and the control channel's `ui.at {handle}`) can grab them.
|
||||
static HANDLES: RefCell<Vec<(String, egui::Pos2)>> = const { RefCell::new(Vec::new()) };
|
||||
/// Painted parts with no widget of their own (the toolbar, the workspace switcher…): rects
|
||||
/// and counts by name, for checks.
|
||||
static RECTS: RefCell<Vec<(String, egui::Rect)>> = const { RefCell::new(Vec::new()) };
|
||||
static COUNTS: RefCell<Vec<(String, f64)>> = const { RefCell::new(Vec::new()) };
|
||||
/// Widths a scenario's `same_width` checks remembered (by rect name).
|
||||
static WIDTHS: RefCell<Vec<(String, f32)>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// A manipulator's handle drawn this frame at `at` (screen), by name: "arrow" (a dialog's value
|
||||
/// arrow), "triad_x"… Call it from the code that draws the handle, every frame it shows.
|
||||
pub fn publish_handle(name: &str, at: egui::Pos2) {
|
||||
// The latest wins (lookups search from the end), so publishing is cheap for browser rows.
|
||||
HANDLES.with(|h| h.borrow_mut().push((name.to_string(), at)));
|
||||
}
|
||||
|
||||
/// Publish a rectangle and its centered interaction handle with the same explicit identity.
|
||||
pub(crate) fn publish_target(name: &str, rect: egui::Rect) {
|
||||
publish_target_as(name, name, rect);
|
||||
}
|
||||
|
||||
/// Publish centered geometry with explicit, independently named handle and rectangle aliases.
|
||||
pub(crate) fn publish_target_as(handle: &str, rect_name: &str, rect: egui::Rect) {
|
||||
publish_handle(handle, rect.center());
|
||||
publish_rect(rect_name, rect);
|
||||
}
|
||||
|
||||
/// Where a handle was drawn last frame.
|
||||
pub fn handle_at(name: &str) -> Option<egui::Pos2> {
|
||||
HANDLES.with(|h| h.borrow().iter().rfind(|(n, _)| n == name).map(|x| x.1))
|
||||
}
|
||||
|
||||
/// Every handle drawn last frame whose name starts with `prefix`.
|
||||
pub fn handles(prefix: &str) -> Vec<(String, egui::Pos2)> {
|
||||
HANDLES.with(|h| h.borrow().iter().filter(|(n, _)| n.starts_with(prefix)).cloned().collect())
|
||||
}
|
||||
|
||||
/// A painted part's rect this frame, by name ("toolbar", "workspace_switcher").
|
||||
pub fn publish_rect(name: &str, r: egui::Rect) {
|
||||
RECTS.with(|h| h.borrow_mut().push((name.to_string(), r)));
|
||||
}
|
||||
|
||||
pub fn rect_of(name: &str) -> Option<egui::Rect> {
|
||||
RECTS.with(|h| h.borrow().iter().rfind(|(n, _)| n == name).map(|x| x.1))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn rects(prefix: &str) -> Vec<(String, egui::Rect)> {
|
||||
RECTS.with(|r| r.borrow().iter().filter(|(name, _)| name.starts_with(prefix)).cloned().collect())
|
||||
}
|
||||
|
||||
/// A number about this frame's drawing, by name ("axis_view_highlights": the axis view's highlighted tips).
|
||||
pub fn publish_count(name: &str, v: f64) {
|
||||
COUNTS.with(|h| h.borrow_mut().push((name.to_string(), v)));
|
||||
}
|
||||
|
||||
pub fn count_of(name: &str) -> Option<f64> {
|
||||
COUNTS.with(|h| h.borrow().iter().rfind(|(n, _)| n == name).map(|x| x.1))
|
||||
}
|
||||
|
||||
/// Forget last frame's handles, rects and counts (called as a frame starts).
|
||||
pub fn clear_handles() {
|
||||
HANDLES.with(|h| h.borrow_mut().clear());
|
||||
RECTS.with(|h| h.borrow_mut().clear());
|
||||
COUNTS.with(|h| h.borrow_mut().clear());
|
||||
}
|
||||
|
||||
/// `v` with "$DIR" in its strings replaced by `dir`.
|
||||
fn with_dir(v: Value, dir: &str) -> Value {
|
||||
match v {
|
||||
@@ -671,7 +612,7 @@ impl Harness {
|
||||
eprintln!("widgets: {texts:?}");
|
||||
}
|
||||
if s["debug"] == json!("handles") {
|
||||
let names: Vec<String> = HANDLES.with(|h| h.borrow().iter().map(|(n, p)| format!("{n} @{:.0},{:.0}", p.x, p.y)).collect());
|
||||
let names: Vec<String> = handles("").iter().map(|(n, p)| format!("{n} @{:.0},{:.0}", p.x, p.y)).collect();
|
||||
eprintln!("handles: {names:?}");
|
||||
}
|
||||
return Ok(());
|
||||
|
||||
@@ -630,7 +630,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let mut picked = None;
|
||||
let screen = ctx.content_rect();
|
||||
let margins = crate::ui_kit::Menu::frame().total_margin().sum();
|
||||
let width = crate::context_menu::LIST_W.min(screen.width());
|
||||
let width = crate::ui_kit::LIST_W.min(screen.width());
|
||||
let w = (width - margins.x).max(0.0);
|
||||
let header = 20.0;
|
||||
let gap = vec2(12.0, 12.0);
|
||||
|
||||
@@ -664,7 +664,7 @@ pub(crate) fn section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// Searching for "profiles" must keep this entry on the results page; without a hit here the
|
||||
// whole section was dropped and the name field could not be reached with a query active.
|
||||
let entry = [app.tr("prefs.general.profile_name"), app.tr("prefs.general.save_new"), app.tr("prefs.general.import")];
|
||||
if crate::prefs_search::visible(ui, &entry) {
|
||||
if crate::ui_kit::settings_search::visible(ui, &entry) {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let mut draft = std::mem::take(&mut app.settings_profiles.draft);
|
||||
let enter = ui_kit::named_control(ui, app.tr("prefs.general.profile_name"), |ui| {
|
||||
@@ -759,7 +759,7 @@ pub(crate) fn rename_popup(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
return;
|
||||
}
|
||||
let Some(mut rename) = app.settings_profiles.rename.take() else { return };
|
||||
crate::prefs_search::note_key_sink(ctx);
|
||||
crate::ui_kit::settings_search::note_key_sink(ctx);
|
||||
let mut done = None;
|
||||
let popup = ui_kit::Container::new(ctx, ui_kit::Surface::Popup)
|
||||
.area(egui::Id::unique("sc_profile_rename"))
|
||||
|
||||
@@ -2530,7 +2530,7 @@ fn status_bar_mouse_mapping_opens_the_mappings_and_picks_one_as_the_preferences_
|
||||
assert_eq!(h.app.session.undo.len(), undo);
|
||||
// An unknown mapping changes nothing.
|
||||
let before = serde_json::to_value(&h.app.preferences).unwrap();
|
||||
let hostile = crate::context_menu::Item::action("ui.navPreset", "Nope", "").with(json!({"preset": "nope"}));
|
||||
let hostile = crate::ui_kit::Item::action("ui.navPreset", "Nope", "").with(json!({"preset": "nope"}));
|
||||
crate::context_menu::run_item(&mut h.app, &hostile, egui::pos2(0.0, 0.0));
|
||||
assert_eq!(serde_json::to_value(&h.app.preferences).unwrap(), before);
|
||||
// A second click on the button closes the menu it opened.
|
||||
|
||||
@@ -614,10 +614,10 @@ pub fn rows(ui: &mut egui::Ui, k: &mut Mod, doc: &Document) -> bool {
|
||||
// The size in millimetres follows the factor and the other way round; the aspect is kept.
|
||||
if let Some((_, size)) = frame {
|
||||
for (label, natural) in [("Width", size[0]), ("Height", size[1])] {
|
||||
let k = crate::prefs::parse_number(factor.trim()).filter(|k| *k > 0.0).unwrap_or(1.0);
|
||||
let k = crate::ui_kit::parse_number(factor.trim()).filter(|k| *k > 0.0).unwrap_or(1.0);
|
||||
let mut shown = format!("{} mm", crate::sketch_transform_ui::plain(natural * k));
|
||||
if number(ui, label, &mut shown)
|
||||
&& let Some(mm) = crate::prefs::parse_number(shown.trim_end_matches("mm").trim())
|
||||
&& let Some(mm) = crate::ui_kit::parse_number(shown.trim_end_matches("mm").trim())
|
||||
&& mm > 0.0
|
||||
&& natural > 1e-9
|
||||
{
|
||||
|
||||
+28
-29
@@ -20,7 +20,7 @@ thread_local! {
|
||||
/// must not change one another's palette while a frame or icon texture is being built.
|
||||
static DARK: Cell<bool> = const { Cell::new(true) };
|
||||
/// The colour vision mode, per thread. Each app
|
||||
/// sets it from its own preferences at the start of every frame (`SolveApp::logic`), on the
|
||||
/// sets it from its own preferences at the start of every frame (in its frame logic), on the
|
||||
/// thread that draws it. Tests run many apps side by side on their own threads in one process:
|
||||
/// with one global, a test in a mode changed the colours under another one
|
||||
/// (`hover_stays_off_during_spin_and_animated_views` failed that way). No worker thread reads
|
||||
@@ -580,7 +580,11 @@ pub(crate) fn scoped_tokens(tokens: Tokens) -> impl Drop {
|
||||
}
|
||||
|
||||
/// Temporarily bind a panel's paint palette without publishing a theme change.
|
||||
pub(crate) fn scoped_panel_tokens(tokens: Tokens) -> impl Drop {
|
||||
///
|
||||
/// `Tokens::get_in` returns these tokens first until the returned guard drops, which restores
|
||||
/// the previous override. A host uses it to draw any component in another palette or accent
|
||||
/// (for example another workspace's) without editing the component.
|
||||
pub fn scoped_panel_tokens(tokens: Tokens) -> impl Drop {
|
||||
struct Restore {
|
||||
previous: Option<Tokens>,
|
||||
// The override belongs to this UI thread, so its guard must not move to another one.
|
||||
@@ -791,11 +795,13 @@ impl TabAccent {
|
||||
ctx.data(|d| d.get_temp::<Option<&'static str>>(egui::Id::unique("sc_tab_icon_accent"))).flatten()
|
||||
}
|
||||
|
||||
pub fn publish(app: &crate::SolveApp, ctx: &egui::Context) {
|
||||
/// Publish the accent of `tab` for this frame. `sketch_open` is whether a sketch is being
|
||||
/// edited, `tool_active` whether a tool or dialog is open; both only steer focus painting.
|
||||
pub fn publish(tab: &str, sketch_open: bool, tool_active: bool, ctx: &egui::Context) {
|
||||
let now = ctx.input(|i| i.time);
|
||||
let (published, accent, fading, style_surfaces) = ctx.data_mut(|data| {
|
||||
let state = data.get_temp_mut_or_default::<TabThemeState>(egui::Id::unique(TAB_STATE));
|
||||
let (published, accent, fading) = state.publish(&app.ui.tab, now);
|
||||
let (published, accent, fading) = state.publish(tab, now);
|
||||
let style_surfaces = published.tab_surfaces || state.styled_surfaces;
|
||||
state.styled_surfaces = published.tab_surfaces;
|
||||
(published, accent, fading, style_surfaces)
|
||||
@@ -818,9 +824,9 @@ impl TabAccent {
|
||||
}
|
||||
ctx.data_mut(|d| {
|
||||
d.insert_temp(egui::Id::unique("sc_tab_accent"), accent);
|
||||
d.insert_temp(egui::Id::unique("sc_tab_icon_accent"), icon_accent_for_tab(&app.ui.tab));
|
||||
d.insert_temp(egui::Id::unique("sc_sketch_open"), app.session.active_sketch.is_some());
|
||||
d.insert_temp(egui::Id::unique("sc_tool_accent"), app.tool.is_some() || app.dialog.is_some());
|
||||
d.insert_temp(egui::Id::unique("sc_tab_icon_accent"), icon_accent_for_tab(tab));
|
||||
d.insert_temp(egui::Id::unique("sc_sketch_open"), sketch_open);
|
||||
d.insert_temp(egui::Id::unique("sc_tool_accent"), tool_active);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1958,15 +1964,13 @@ mod tests {
|
||||
#[test]
|
||||
fn tabs_publish_requests_repaints_only_during_fade_and_zero_is_instant() {
|
||||
use crate::theme_files::OptionValue;
|
||||
let mut app = crate::SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut state = tabs_state("dark", BTreeMap::new());
|
||||
state.publish("MODEL", 0.0);
|
||||
ctx.data_mut(|d| d.insert_temp(egui::Id::unique(TAB_STATE), state));
|
||||
app.ui.tab = "SKETCH".into();
|
||||
let frame = |time| {
|
||||
let mut out = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| {
|
||||
TabAccent::publish(&app, ui.ctx());
|
||||
TabAccent::publish("SKETCH", false, false, ui.ctx());
|
||||
crate::gpu::paint_background(ui.painter(), ui.max_rect(), &Tokens::get());
|
||||
});
|
||||
let colors: Vec<_> = out
|
||||
@@ -2175,7 +2179,7 @@ mod tests {
|
||||
themes.extend(crate::theme_files::builtin().iter().map(|theme| (theme.name.as_str(), crate::theme_files::resolve(theme))));
|
||||
let mut failures = Vec::new();
|
||||
for (name, tokens) in themes {
|
||||
for tab in crate::workspace::TABS.iter().copied().chain(["SKETCH", "MESH", "FORM", "SHEET METAL", "RENDER"]) {
|
||||
for tab in super::TAB_NAMES.iter().copied() {
|
||||
let accent = super::TabAccent::for_tab(&tokens, tab);
|
||||
let ratio = super::contrast_ratio(accent.ink, accent.soft);
|
||||
if ratio < 4.5 {
|
||||
@@ -2209,20 +2213,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tab_accent_context_is_per_app_and_does_not_change_widget_visuals() {
|
||||
let mut harness = crate::scenario::Harness::new();
|
||||
let app = &mut harness.app;
|
||||
let first = crate::fonts::test_context();
|
||||
let second = crate::fonts::test_context();
|
||||
let before = first.style_of(egui::Theme::Dark).visuals.clone();
|
||||
app.ui.tab = "SURFACE".into();
|
||||
super::TabAccent::publish(app, &first);
|
||||
super::TabAccent::publish("SURFACE", false, false, &first);
|
||||
assert_eq!(super::TabAccent::current(&first).color, super::Tokens::get().surface_fill);
|
||||
assert_eq!(super::TabAccent::current(&second).color, super::Tokens::get().accent);
|
||||
assert_eq!(super::TabAccent::current_icon_accent(&first), Some("surface"));
|
||||
assert_eq!(super::TabAccent::current_icon_accent(&second), None);
|
||||
assert_eq!(first.style_of(egui::Theme::Dark).visuals, before, "dialog OK, checkboxes and status styles must not inherit the tab");
|
||||
app.ui.tab = "MODEL".into();
|
||||
super::TabAccent::publish(app, &first);
|
||||
super::TabAccent::publish("MODEL", false, false, &first);
|
||||
assert_eq!(super::TabAccent::current(&first).soft, super::Tokens::get().accent_soft);
|
||||
assert_eq!(super::TabAccent::current_icon_accent(&first), None);
|
||||
}
|
||||
@@ -2232,7 +2232,6 @@ mod tests {
|
||||
use crate::theme_files::{OptionValue, ThemeFile};
|
||||
for base in ["dark", "light"] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut app = crate::SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
let file = crate::theme_files::tab_fixture(base);
|
||||
set_dark(file.base.is_dark());
|
||||
set_vision(ColorVision::Off);
|
||||
@@ -2247,19 +2246,19 @@ mod tests {
|
||||
});
|
||||
set_custom(custom)
|
||||
};
|
||||
let publish = |app: &crate::SolveApp, time| {
|
||||
ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| TabAccent::publish(app, ui.ctx())).textures_delta.clear();
|
||||
let publish = |tab: &str, time| {
|
||||
ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| TabAccent::publish(tab, false, false, ui.ctx()))
|
||||
.textures_delta
|
||||
.clear();
|
||||
};
|
||||
prepare(&file, Some(true));
|
||||
apply(&ctx);
|
||||
let before = ctx.global_style().visuals.clone();
|
||||
let base_custom = CUSTOM.get();
|
||||
app.ui.tab = "MODEL".into();
|
||||
publish(&app, 0.0);
|
||||
publish("MODEL", 0.0);
|
||||
assert_eq!(ctx.global_style().visuals, before);
|
||||
app.ui.tab = "SKETCH".into();
|
||||
publish(&app, 1.0);
|
||||
publish(&app, 1.12);
|
||||
publish("SKETCH", 1.0);
|
||||
publish("SKETCH", 1.12);
|
||||
let panel = Tokens::get().panel;
|
||||
assert_ne!(panel, before.panel_fill, "exercise an actual animated panel fill");
|
||||
let mut animated = before.clone();
|
||||
@@ -2268,24 +2267,24 @@ mod tests {
|
||||
assert_eq!(ctx.global_style().visuals, animated, "all non-surface widget/selection/status visuals stay unchanged");
|
||||
// Repair either owned fill independently, even when the panel already matches.
|
||||
ctx.global_style_mut(|style| style.visuals.window_fill = before.window_fill);
|
||||
publish(&app, 1.12);
|
||||
publish("SKETCH", 1.12);
|
||||
assert_eq!(ctx.global_style().visuals, animated);
|
||||
|
||||
assert!(!prepare(&file, Some(false)), "toggling surfaces does not change the base Custom tokens");
|
||||
assert_eq!(CUSTOM.get(), base_custom);
|
||||
// Deliberately omit apply(): a full restyle would conceal stale animated fills.
|
||||
publish(&app, 1.13);
|
||||
publish("SKETCH", 1.13);
|
||||
assert_eq!(ctx.global_style().visuals, before, "turning surfaces off must restore both base fills");
|
||||
|
||||
assert!(!prepare(&file, Some(true)));
|
||||
publish(&app, 2.0);
|
||||
publish("SKETCH", 2.0);
|
||||
assert_ne!(ctx.global_style().visuals.panel_fill, before.panel_fill);
|
||||
let mut plain = file.clone();
|
||||
plain.tabs.clear();
|
||||
plain.options.clear();
|
||||
assert!(!prepare(&plain, None), "a no-option theme can retain the exact same base tokens");
|
||||
assert_eq!(CUSTOM.get(), base_custom);
|
||||
publish(&app, 2.1);
|
||||
publish("SKETCH", 2.1);
|
||||
assert_eq!(ctx.global_style().visuals, before, "restoring a no-option theme must release the surface fills");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ use solidcraft_engine::doc::{
|
||||
use solidcraft_engine::schema::edit::PlaybackAction;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::context_menu::Item;
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::Item;
|
||||
|
||||
/// Apply transport actions from the current marker; endpoints stay bounded, including an
|
||||
/// empty design. All orientations reach the same rollback command.
|
||||
@@ -52,14 +52,14 @@ pub(crate) fn transport_response(app: &mut SolveApp, control: crate::ui_kit::His
|
||||
|
||||
/// The History controls' menu: shows or hides the Play button, and — when the design marks any
|
||||
/// component out of the bar — shows or hides those components' entries (#10393).
|
||||
pub(crate) fn transport_items(app: &SolveApp) -> Vec<crate::context_menu::Item> {
|
||||
pub(crate) fn transport_items(app: &SolveApp) -> Vec<crate::ui_kit::Item> {
|
||||
let show = !app.preferences.history_play_button;
|
||||
let label = crate::i18n::tr(if show { "ui.history_play_button_show" } else { "ui.history_play_button_hide" });
|
||||
let mut items = vec![crate::context_menu::Item::action("ui.historyPlayButton", label, "timeline_play").with(json!({ "show": show }))];
|
||||
let mut items = vec![crate::ui_kit::Item::action("ui.historyPlayButton", label, "timeline_play").with(json!({ "show": show }))];
|
||||
if app.session.doc.components.iter().any(|c| c.history_hidden) {
|
||||
let show_hidden = !app.tree.history_show_hidden;
|
||||
let label = crate::i18n::tr(if show_hidden { "ui.history_show_hidden" } else { "ui.history_hide_hidden" });
|
||||
items.push(crate::context_menu::Item::action("ui.historyShowHidden", label, "eye").with(json!({ "show": show_hidden })));
|
||||
items.push(crate::ui_kit::Item::action("ui.historyShowHidden", label, "eye").with(json!({ "show": show_hidden })));
|
||||
}
|
||||
items
|
||||
}
|
||||
@@ -1397,7 +1397,7 @@ mod tests {
|
||||
assert!(handle_at("timeline:CopyBox").is_none(), "the hidden copy leaves the bar");
|
||||
assert!(handle_at("timeline:PartBox").is_some(), "the rest of the design stays");
|
||||
// The History controls' menu shows them anyway, and hides them again (#10393).
|
||||
fn toggle(h: &Harness) -> context_menu::Item {
|
||||
fn toggle(h: &Harness) -> crate::ui_kit::Item {
|
||||
let items = context_menu::items(&h.app, &Target::HistoryControls);
|
||||
items.into_iter().find(|i| i.id == "ui.historyShowHidden").expect("the bar offers to show hidden entries").clone()
|
||||
}
|
||||
|
||||
@@ -759,7 +759,7 @@ fn file_menu(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::NONE;
|
||||
ui.visuals_mut().widgets.hovered.weak_bg_fill = Tokens::get().hover;
|
||||
let item_if = |ui: &mut egui::Ui, label: &str, key: &str, icon: &str, enabled: bool| {
|
||||
crate::context_menu::list_row(ui, &crate::context_menu::Item::action(label, label, icon).key(key).on_if(enabled)).is_some()
|
||||
crate::context_menu::list_row(ui, &crate::ui_kit::Item::action(label, label, icon).key(key).on_if(enabled)).is_some()
|
||||
};
|
||||
let item = |ui: &mut egui::Ui, label: &str, key: &str, icon: &str| item_if(ui, label, key, icon, true);
|
||||
// Insert, Save and Export act on the design, which Home hides.
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::collections::BTreeMap;
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::{CommandSpec, command_specs};
|
||||
|
||||
use crate::context_menu::Item;
|
||||
use crate::ui_kit::Item;
|
||||
use crate::{SolveApp, ui_kit};
|
||||
|
||||
/// The only workspace so far.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Actions elements for dense cards; see `docs/ui-kit.md` for tokens and usage.
|
||||
use super::metrics::*;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{fonts::RichTextExt as _, scenario, theme::Tokens};
|
||||
use crate::{fonts::RichTextExt as _, theme::Tokens};
|
||||
use egui::{Response, RichText, Ui, vec2};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
@@ -182,7 +182,7 @@ pub(crate) fn text_button(ui: &mut Ui, label: &str, enabled: bool) -> Response {
|
||||
enabled,
|
||||
egui::Button::new(RichText::new(label).size(t.font_label).color(t.form_ink)).frame(false).min_size(vec2(0.0, t.control_height)),
|
||||
);
|
||||
scenario::publish_target(&format!("dialog:action:{label}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:action:{label}"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -241,15 +241,15 @@ pub(super) fn footer_with_left(
|
||||
t.dialog_footer,
|
||||
);
|
||||
t.secondary_hline(ui.painter(), band.x_range(), band.top(), t.dialog_rule);
|
||||
scenario::publish_rect("dialog:footer", band);
|
||||
scenario::publish_count("dialog:ok:enabled", f64::from(ok_enabled && ui.is_enabled()));
|
||||
solidcraft_ui_registry::publish_rect("dialog:footer", band);
|
||||
solidcraft_ui_registry::publish_count("dialog:ok:enabled", f64::from(ok_enabled && ui.is_enabled()));
|
||||
let inner = band.shrink2(vec2(t.space_12, t.space_10));
|
||||
scenario::publish_rect("dialog:footer:actions", inner);
|
||||
solidcraft_ui_registry::publish_rect("dialog:footer:actions", inner);
|
||||
let mut action = if ui.is_enabled() { keyboard(ui.ctx(), ok_enabled) } else { Action::None };
|
||||
let look = ActionLook::new(ok_enabled, !nothing_to_apply, selected, danger);
|
||||
if apply_label.is_some() {
|
||||
let live = if confirm { !nothing_to_apply } else { look.live(Action::Apply) };
|
||||
scenario::publish_count("dialog:apply:enabled", f64::from(live && ui.is_enabled()));
|
||||
solidcraft_ui_registry::publish_count("dialog:apply:enabled", f64::from(live && ui.is_enabled()));
|
||||
}
|
||||
let mut actions_left = inner.right();
|
||||
let row = ui.scope_builder(egui::UiBuilder::new().max_rect(inner).layout(egui::Layout::left_to_right(egui::Align::Center)), |ui| {
|
||||
@@ -299,8 +299,8 @@ pub(super) fn footer_with_left(
|
||||
ui.add(button)
|
||||
});
|
||||
pointer_on_hover(ui, &response);
|
||||
scenario::publish_target("dialog:apply", response.rect);
|
||||
scenario::publish_handle(&format!("dialog:{label}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_target("dialog:apply", response.rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{label}"), response.rect.center());
|
||||
if response.clicked() {
|
||||
action = Action::Apply;
|
||||
}
|
||||
@@ -379,8 +379,8 @@ fn action_button(ui: &mut Ui, label: &str, kind: Action, look: &ActionLook, padd
|
||||
Action::Apply => "dialog:apply",
|
||||
_ => "dialog:cancel",
|
||||
};
|
||||
scenario::publish_target(key, response.rect);
|
||||
scenario::publish_handle(&format!("dialog:{label}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_target(key, response.rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{label}"), response.rect.center());
|
||||
response
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ impl<'a> HeaderActions<'a> {
|
||||
/// Draws the buttons; returns what was clicked, or what Enter and Esc ask for.
|
||||
pub(crate) fn show(&self, ui: &mut Ui, ok_enabled: bool) -> Action {
|
||||
let ok_enabled = ok_enabled && self.rect(Action::Ok).is_some();
|
||||
scenario::publish_count("dialog:ok:enabled", f64::from(ok_enabled && ui.is_enabled()));
|
||||
solidcraft_ui_registry::publish_count("dialog:ok:enabled", f64::from(ok_enabled && ui.is_enabled()));
|
||||
let mut action = if ui.is_enabled() { keyboard(ui.ctx(), ok_enabled) } else { Action::None };
|
||||
let look = ActionLook::new(ok_enabled, true, None, false);
|
||||
let padding = Tokens::get().space_8;
|
||||
@@ -442,7 +442,7 @@ impl<'a> HeaderActions<'a> {
|
||||
}
|
||||
}
|
||||
if let Some(all) = self.slots.iter().map(|(_, _, rect)| *rect).reduce(|a, b| a.union(b)) {
|
||||
scenario::publish_rect("dialog:header:actions", all);
|
||||
solidcraft_ui_registry::publish_rect("dialog:header:actions", all);
|
||||
}
|
||||
action
|
||||
}
|
||||
@@ -500,7 +500,7 @@ pub(crate) fn button_pair(ui: &mut egui::Ui, key: &str, states: [bool; 2], butto
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), on, label));
|
||||
t.button_bevel(ui, &response, on);
|
||||
pointer_on_hover(ui, &response);
|
||||
crate::scenario::publish_target(&format!("{key}:{}", button.key), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{key}:{}", button.key), rect);
|
||||
if let Some(value) = clicked.get_mut(index) {
|
||||
*value = response.backing_on_hover_text(label).clicked();
|
||||
}
|
||||
@@ -572,8 +572,8 @@ fn button_widget(label: egui::WidgetText, options: Button) -> impl egui::Widget
|
||||
pub fn button(ui: &mut Ui, label: impl Into<egui::WidgetText>, options: Button) -> Response {
|
||||
let label = label.into();
|
||||
let key = label.text().to_owned();
|
||||
if crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[&key]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
if super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[&key]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let response = match options {
|
||||
Button::Enabled(enabled) if super::settings_page_active(ui) => {
|
||||
@@ -621,7 +621,7 @@ pub(crate) fn button_at(ui: &mut Ui, id: &str, rect: egui::Rect, label: impl Int
|
||||
/// Accent action outside the footer, using the same shared button component.
|
||||
pub(crate) fn primary_button(ui: &mut Ui, label: &str) -> Response {
|
||||
let response = button(ui, crate::i18n::tr_label(label), Button::Primary);
|
||||
scenario::publish_target(&format!("dialog:button:{label}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:button:{label}"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -649,13 +649,13 @@ mod tests;
|
||||
// Additional identities must never replace an existing field handle in compound rows.
|
||||
fn publish_button(ui: &Ui, response: &Response, label: &str) {
|
||||
let key = format!("dialog:button:{}:{label}", super::layout::label(ui));
|
||||
scenario::publish_target(&key, response.rect);
|
||||
solidcraft_ui_registry::publish_target(&key, response.rect);
|
||||
}
|
||||
|
||||
/// Nested action menu in a dense table. Tokens: scope control/font tokens.
|
||||
pub(crate) fn action_menu<R>(ui: &mut Ui, label: &str, add: impl FnOnce(&mut Ui) -> R) -> egui::InnerResponse<Option<R>> {
|
||||
let response = super::Menu::button(ui, crate::i18n::tr_label(label), |ui| super::scope(ui, add));
|
||||
scenario::publish_target(&format!("dialog:menu:{label}"), response.response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:menu:{label}"), response.response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -747,7 +747,7 @@ pub(crate) fn selection_actions(ui: &mut Ui, name: &str, actions: &[super::Stage
|
||||
let (rect, name_response) = ui.allocate_exact_size(vec2(width, t.control_height - t.space_2), egui::Sense::click());
|
||||
name_response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), name));
|
||||
super::text::paint_text(ui, rect.shrink2(vec2(t.space_6, 0.0)), name, font, accent.ink, false);
|
||||
scenario::publish_target("selection:name", rect);
|
||||
solidcraft_ui_registry::publish_target("selection:name", rect);
|
||||
let mut clicked = name_response.clicked().then_some(actions.len());
|
||||
for (index, action) in actions.iter().enumerate() {
|
||||
let response = ui
|
||||
@@ -756,7 +756,7 @@ pub(crate) fn selection_actions(ui: &mut Ui, name: &str, actions: &[super::Stage
|
||||
icon_button_colored(ui, rect, action.icon.unwrap_or("inspect"), action.label, action.primary.then_some(accent.ink))
|
||||
})
|
||||
.inner;
|
||||
scenario::publish_target(&format!("selection:action:{index}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("selection:action:{index}"), response.rect);
|
||||
if response.clicked() {
|
||||
clicked = Some(index);
|
||||
}
|
||||
@@ -827,8 +827,8 @@ fn icon_label_presentation(ui: &mut Ui, label: &str, icon: Option<&str>, enabled
|
||||
// A scope cannot reserve its natural width in the parent's wrapping row.
|
||||
let response = if super::settings_page_active(ui) { ui.add_enabled(enabled, paint) } else { ui.add_enabled_ui(enabled, paint).inner };
|
||||
let key = format!("dialog:button:{}:{label}", super::layout::label(ui));
|
||||
scenario::publish_handle(&key, response.rect.center());
|
||||
scenario::publish_rect(&key, response.rect);
|
||||
solidcraft_ui_registry::publish_handle(&key, response.rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&key, response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ pub(crate) fn assistant_approval<R>(ui: &mut Ui, title: &str, body: impl FnOnce(
|
||||
.inner
|
||||
},
|
||||
);
|
||||
crate::scenario::publish_rect("assistant:approval", output.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("assistant:approval", output.response.rect);
|
||||
output
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ pub(crate) fn checkbox(ui: &mut Ui, label: &str, value: &mut bool) -> Response {
|
||||
/// Mixed values become checked when activated; the caller owns whether selection is mixed.
|
||||
/// Tokens: space_14/3/2/6, radius_sm, control_fill/border, form_primary/ink, hover, on_accent.
|
||||
pub(crate) fn checkbox_indeterminate(ui: &mut Ui, label: &str, value: &mut bool, mixed: bool) -> Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[label]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[label]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[label]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[label]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let font = egui::FontId::proportional(t.font_label);
|
||||
@@ -75,8 +75,8 @@ pub(crate) fn checkbox_indeterminate(ui: &mut Ui, label: &str, value: &mut bool,
|
||||
let width = (t.space_14 + gap + galley.size().x).max(ui.spacing().interact_size.y).min(super::metrics::dimension(ui.available_width()));
|
||||
let height = ui.spacing().interact_size.y.max(t.space_14).max(galley.size().y);
|
||||
let (rect, mut response) = ui.allocate_exact_size(vec2(width, height), egui::Sense::click());
|
||||
crate::prefs_search::remember(ui, label, rect);
|
||||
crate::prefs_search::follow(ui, label, rect);
|
||||
super::settings_search::remember(ui, label, rect);
|
||||
super::settings_search::follow(ui, label, rect);
|
||||
if response.clicked() {
|
||||
*value = mixed || !*value;
|
||||
response.mark_changed();
|
||||
|
||||
@@ -5,9 +5,9 @@ use super::{
|
||||
metrics::*,
|
||||
text::paint_text,
|
||||
};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Ui, vec2};
|
||||
|
||||
/// Text segments retain mixed states and optional compact clipping instead of dropdown fallback.
|
||||
@@ -36,12 +36,12 @@ pub(crate) struct Segments<'a> {
|
||||
}
|
||||
|
||||
pub(crate) fn segmented(ui: &mut Ui, selected: &mut usize, options: &[&str], config: Segments<'_>) -> bool {
|
||||
crate::prefs_search::note_unlabelled(ui, options);
|
||||
if crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, options) {
|
||||
super::settings_search::note_unlabelled(ui, options);
|
||||
if super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, options) {
|
||||
return false;
|
||||
}
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::shows_all(ui) {
|
||||
crate::prefs_search::reveal(ui);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::shows_all(ui) {
|
||||
super::settings_search::reveal(ui);
|
||||
}
|
||||
let Segments {
|
||||
focus,
|
||||
@@ -194,11 +194,11 @@ pub(crate) fn segmented(ui: &mut Ui, selected: &mut usize, options: &[&str], con
|
||||
);
|
||||
t.button_bevel_with_radius(ui, &response, on, rounding);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, segment_enabled, on, *text));
|
||||
scenario::publish_target(&format!("dialog:{}:{option}", label(ui)), segment);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:{}:{option}", label(ui)), segment);
|
||||
if let Some(target) = targets.get(index) {
|
||||
let visible = segment.intersect(ui.clip_rect());
|
||||
if visible.is_positive() {
|
||||
scenario::publish_target(target, visible);
|
||||
solidcraft_ui_registry::publish_target(target, visible);
|
||||
}
|
||||
}
|
||||
let response =
|
||||
@@ -216,7 +216,7 @@ pub(crate) fn segmented(ui: &mut Ui, selected: &mut usize, options: &[&str], con
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
}
|
||||
scenario::publish_target_as(&format!("dialog:{}", label(ui)), &format!("dialog:segments:{}", label(ui)), rect);
|
||||
solidcraft_ui_registry::publish_target_as(&format!("dialog:{}", label(ui)), &format!("dialog:segments:{}", label(ui)), rect);
|
||||
clicked
|
||||
}
|
||||
|
||||
@@ -280,8 +280,8 @@ pub(crate) fn segmented_icons(ui: &mut Ui, selected: &mut usize, options: &[Icon
|
||||
}
|
||||
t.button_bevel_with_radius(ui, &response, on, rounding);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), on, option.key));
|
||||
scenario::publish_handle(&format!("dialog:{}:{}", label(ui), option.key), segment.center());
|
||||
scenario::publish_target(option.key, segment);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{}:{}", label(ui), option.key), segment.center());
|
||||
solidcraft_ui_registry::publish_target(option.key, segment);
|
||||
let response = response.backing_on_hover_text(&option.tip);
|
||||
if response.clicked() {
|
||||
*selected = index;
|
||||
@@ -291,7 +291,7 @@ pub(crate) fn segmented_icons(ui: &mut Ui, selected: &mut usize, options: &[Icon
|
||||
if !catalog && t.borders.bevel.is_none() {
|
||||
ui.painter().rect_stroke(rect, t.control_radius, t.control_border_stroke(t.control_border), egui::StrokeKind::Inside);
|
||||
}
|
||||
scenario::publish_target_as(&format!("dialog:{}", label(ui)), &format!("dialog:segments:{}", label(ui)), rect);
|
||||
solidcraft_ui_registry::publish_target_as(&format!("dialog:{}", label(ui)), &format!("dialog:segments:{}", label(ui)), rect);
|
||||
clicked
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ pub(crate) fn checkbox_grid(ui: &mut Ui, items: &mut [(&str, &mut bool)], column
|
||||
let text_rect = egui::Rect::from_min_max(egui::pos2((square.right() + t.space_6).min(rect.right()), rect.top()), rect.max);
|
||||
paint_text(ui, text_rect, text, egui::FontId::proportional(t.font_body), t.form_ink, false);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::CheckBox, ui.is_enabled(), **value, translated));
|
||||
scenario::publish_target_as(&format!("dialog:{label}"), &format!("dialog:checkbox:{label}"), rect);
|
||||
solidcraft_ui_registry::publish_target_as(&format!("dialog:{label}"), &format!("dialog:checkbox:{label}"), rect);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -344,7 +344,7 @@ pub(crate) fn checkbox_row(ui: &mut egui::Ui, key: &str, label: &str, on: bool)
|
||||
.with_clip_rect(ui.clip_rect().intersect(egui::Rect::from_min_max(rect.min, egui::pos2(square.left() - t.space_6, rect.bottom()))));
|
||||
painter.text(rect.left_center(), egui::Align2::LEFT_CENTER, label, egui::FontId::proportional(t.font_label), t.form_ink);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::CheckBox, ui.is_enabled(), on, label));
|
||||
crate::scenario::publish_target(&format!("sketch_palette:toggle:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("sketch_palette:toggle:{key}"), rect);
|
||||
response.backing_on_hover_text(label).clicked()
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ pub(crate) fn text_emphasis(ui: &mut Ui, bold: &mut bool, italic: &mut bool, bol
|
||||
response = response.backing_on_disabled_hover_text(crate::i18n::tr("sketch_text.no_bold"));
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled() && enabled, *value, crate::i18n::tr(id)));
|
||||
scenario::publish_target_as(&format!("dialog:{key}"), &format!("dialog:emphasis:{key}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target_as(&format!("dialog:{key}"), &format!("dialog:emphasis:{key}"), response.rect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ pub(crate) fn tool_choices<'a>(ui: &mut Ui, label: &str, current: &str, choices:
|
||||
);
|
||||
crate::icons::paint_icon(ui.painter(), response.rect.shrink(t.space_6), choice.icon, f32::from(t.icon.a()) / 255.0);
|
||||
t.button_bevel(ui, &response, on);
|
||||
scenario::publish_target(&format!("sketch_palette:feature:{}", choice.key), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("sketch_palette:feature:{}", choice.key), response.rect);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, enabled, on, choice.label));
|
||||
if response.backing_on_hover_text(choice.label).clicked() && enabled {
|
||||
start = Some(choice.key);
|
||||
@@ -418,11 +418,11 @@ pub(crate) fn toggle_icon(ui: &mut Ui, row: &str, label: &str, selected: &mut bo
|
||||
crate::icons::paint_icon(ui.painter(), response.rect.shrink(t.space_3), icon, f32::from(t.icon.a()) / 255.0);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, true, *selected, crate::i18n::tr_label(label)));
|
||||
t.button_bevel(ui, &response, *selected);
|
||||
crate::scenario::publish_handle(&format!("dialog:{row}:{label}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{row}:{label}"), response.rect.center());
|
||||
if response.clone().backing_on_hover_text(crate::i18n::tr_label(label)).clicked() {
|
||||
*selected = !*selected;
|
||||
}
|
||||
scenario::publish_rect(&format!("dialog:{row}:{label}"), response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:{row}:{label}"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -435,15 +435,15 @@ mod tests;
|
||||
pub(crate) fn list_picker(ui: &mut Ui, key: &str, text: &str, selected: bool) -> egui::Response {
|
||||
let response =
|
||||
ui.add_sized(egui::vec2(super::metrics::dimension(ui.available_width()), super::row_height()), egui::Button::selectable(selected, text));
|
||||
crate::scenario::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
/// Intrinsic-width selection chip for horizontal lists, sharing list-picker styling.
|
||||
pub(crate) fn choice_chip(ui: &mut Ui, key: &str, text: &str, selected: bool) -> egui::Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[text]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[text]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[text]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[text]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
if super::settings_page_active(ui) {
|
||||
@@ -456,9 +456,9 @@ pub(crate) fn choice_chip(ui: &mut Ui, key: &str, text: &str, selected: bool) ->
|
||||
.stroke(egui::Stroke::new(1.0, if selected { t.form_primary } else { t.control_border }))
|
||||
.corner_radius(t.radius_xl),
|
||||
);
|
||||
crate::prefs_search::remember(ui, text, response.rect);
|
||||
crate::prefs_search::follow(ui, text, response.rect);
|
||||
crate::scenario::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
super::settings_search::remember(ui, text, response.rect);
|
||||
super::settings_search::follow(ui, text, response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
return response;
|
||||
}
|
||||
if super::layout::in_catalog(ui) {
|
||||
@@ -475,13 +475,13 @@ pub(crate) fn choice_chip(ui: &mut Ui, key: &str, text: &str, selected: bool) ->
|
||||
false,
|
||||
);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), selected, text));
|
||||
crate::scenario::publish_target(&format!("dialog:list:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:list:{key}"), rect);
|
||||
return response;
|
||||
}
|
||||
let width = (ui.painter().layout_no_wrap(text.to_owned(), crate::fonts::semibold(t.font_label), t.text).size().x + t.space_24)
|
||||
.clamp(t.control_height * 2.0, t.form_card_width);
|
||||
let response = ui.add_sized(vec2(width, super::row_height()), egui::Button::selectable(selected, text).truncate());
|
||||
crate::scenario::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -558,7 +558,7 @@ pub(crate) fn thumbnail_choice(ui: &mut Ui, key: &str, text: &str, state: Thumbn
|
||||
if state.modified {
|
||||
let dot = egui::pos2(rect.right() - t.space_6, rect.top() + t.space_6);
|
||||
ui.painter().circle_filled(dot, t.space_3, t.warning);
|
||||
crate::scenario::publish_rect(&format!("dialog:list:{key}:modified"), egui::Rect::from_center_size(dot, vec2(t.space_6, t.space_6)));
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:list:{key}:modified"), egui::Rect::from_center_size(dot, vec2(t.space_6, t.space_6)));
|
||||
}
|
||||
if let Some(progress) = state.progress {
|
||||
let center = thumb.center();
|
||||
@@ -573,10 +573,10 @@ pub(crate) fn thumbnail_choice(ui: &mut Ui, key: &str, text: &str, state: Thumbn
|
||||
})
|
||||
.collect();
|
||||
ui.painter().add(egui::Shape::line(points, egui::Stroke::new(t.space_2, accent.ink)));
|
||||
crate::scenario::publish_rect(&format!("dialog:list:{key}:progress"), thumb);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:list:{key}:progress"), thumb);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), state.selected, text));
|
||||
crate::scenario::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:list:{key}"), response.rect);
|
||||
response.backing_on_hover_text(text)
|
||||
}
|
||||
|
||||
@@ -745,7 +745,7 @@ pub(crate) fn popup_list(
|
||||
// Tip the full name first: long identifiers still clip inside a capped popup.
|
||||
let tip = if option.detail.is_empty() { option.text.to_owned() } else { format!("{}\n{}", option.text, option.detail) };
|
||||
let response = response.backing_on_hover_text(tip);
|
||||
crate::scenario::publish_handle(&format!("dialog:suggestion:{index}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:suggestion:{index}"), response.rect.center());
|
||||
if response.clicked() {
|
||||
chosen = Some(index);
|
||||
}
|
||||
@@ -759,7 +759,7 @@ pub(crate) fn popup_list(
|
||||
})
|
||||
});
|
||||
});
|
||||
crate::scenario::publish_rect("dialog:suggestions", area.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:suggestions", area.response.rect);
|
||||
ctx.data_mut(|d| d.insert_temp(id.with("rows"), rows));
|
||||
chosen
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//! Document RGB and optional separate opacity editing. Interface paint uses tokens; gradients are colour data.
|
||||
use super::recent_colors::{MAX_RECENT_COLORS, recent_colors, remember_color};
|
||||
use super::{layout::label, metrics::dimension};
|
||||
use crate::recent_colors::{MAX_RECENT_COLORS, recent_colors, remember_color};
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::{i18n, scenario, theme::Tokens};
|
||||
use crate::{i18n, theme::Tokens};
|
||||
use egui::{Color32, Response, Sense, Ui, ecolor::HsvaGamma, pos2, vec2};
|
||||
|
||||
fn publish(key: &str, response: &Response) {
|
||||
scenario::publish_rect(key, response.rect);
|
||||
scenario::publish_handle(key, response.rect.center());
|
||||
solidcraft_ui_registry::publish_rect(key, response.rect);
|
||||
solidcraft_ui_registry::publish_handle(key, response.rect.center());
|
||||
}
|
||||
|
||||
/// Linear RGB swatch and precise channel fields. The preview is clipped to display gamut;
|
||||
@@ -144,7 +144,7 @@ fn color_popover_with_opacity(ui: &mut Ui, rgb: &mut [u8; 3], mut opacity: Optio
|
||||
let rect =
|
||||
egui::Rect::from_min_size(rect.min + vec2(index as f32 * rect.width() / 3.0, 0.0), vec2(rect.width() / 3.0, rect.height()));
|
||||
ui.interact(rect, ui.scope_id().with(("format_tip", index)), Sense::hover()).backing_on_hover_text(i18n::tr(key));
|
||||
scenario::publish_handle(&format!("color:format:{name}"), rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("color:format:{name}"), rect.center());
|
||||
}
|
||||
});
|
||||
hexadecimal = selected == 2;
|
||||
@@ -296,7 +296,7 @@ fn color_popover_with_opacity(ui: &mut Ui, rgb: &mut [u8; 3], mut opacity: Optio
|
||||
}
|
||||
});
|
||||
// A colour joins the recent list once an edit is finished: a drag or a held step button is
|
||||
// released, a typed value is entered. Every picker shares the list (`crate::recent_colors`).
|
||||
// released, a typed value is entered. Every picker shares the list (`super::recent_colors`).
|
||||
// While the popover stays open, fine-tuning replaces the colour it added rather than filling
|
||||
// the list with each step; a colour taken from the list starts a new entry.
|
||||
let session_id = id.with("recent_session");
|
||||
@@ -320,7 +320,7 @@ fn color_popover_with_opacity(ui: &mut Ui, rgb: &mut [u8; 3], mut opacity: Optio
|
||||
d.insert_temp(hex_id, hex);
|
||||
d.insert_temp(format_id, hexadecimal);
|
||||
});
|
||||
scenario::publish_rect("color:popover", ui.min_rect());
|
||||
solidcraft_ui_registry::publish_rect("color:popover", ui.min_rect());
|
||||
*rgb != before_rgb || opacity.as_deref().copied() != before_opacity || hsv != before || accepted_mixed
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ fn spin_channel(ui: &mut Ui, name: &str, tip: Option<&str>, value: &mut f64, spi
|
||||
egui::DragValue::new(value)
|
||||
.range(0.0..=max)
|
||||
.speed(speed)
|
||||
.custom_parser(crate::prefs::parse_number)
|
||||
.custom_parser(super::parse_number)
|
||||
.custom_formatter(move |v, _| format!("{v:.decimals$}")),
|
||||
);
|
||||
response.widget_info(|| {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::scenario;
|
||||
use crate::ui_kit::test_support::pointer;
|
||||
|
||||
fn frame<R>(ctx: &egui::Context, events: Vec<egui::Event>, mut add: impl FnMut(&mut Ui) -> R) {
|
||||
@@ -473,11 +474,11 @@ fn recent_list_is_capped_unique_and_synced_with_the_saved_one() {
|
||||
assert_eq!(recent.iter().filter(|c| **c == [35, 0, 0]).count(), 1);
|
||||
// Picks flow to the saved list once…
|
||||
let mut saved = Vec::new();
|
||||
crate::recent_colors::share(&ctx, &mut saved);
|
||||
crate::ui_kit::recent_colors::share(&ctx, &mut saved);
|
||||
assert_eq!(saved, recent);
|
||||
// …and a list loaded from preferences (even an oversized one) is what the pickers show.
|
||||
let mut loaded: Vec<[u8; 3]> = (0..100u8).map(|i| [0, i, 0]).collect();
|
||||
crate::recent_colors::share(&ctx, &mut loaded);
|
||||
crate::ui_kit::recent_colors::share(&ctx, &mut loaded);
|
||||
assert_eq!(loaded.len(), MAX_RECENT_COLORS);
|
||||
assert_eq!(recent_colors(&ctx), loaded);
|
||||
}
|
||||
@@ -492,9 +493,9 @@ fn recent_colours_are_saved_with_the_preferences_and_hostile_entries_dropped() {
|
||||
assert_eq!(app.ui.recent_colors.len(), MAX_RECENT_COLORS);
|
||||
assert_eq!(app.ui.recent_colors.first(), Some(&[1, 2, 3]));
|
||||
let ctx = crate::fonts::test_context();
|
||||
crate::recent_colors::share(&ctx, &mut app.ui.recent_colors);
|
||||
crate::ui_kit::recent_colors::share(&ctx, &mut app.ui.recent_colors);
|
||||
remember_color(&ctx, [9, 8, 7], None);
|
||||
crate::recent_colors::share(&ctx, &mut app.ui.recent_colors);
|
||||
crate::ui_kit::recent_colors::share(&ctx, &mut app.ui.recent_colors);
|
||||
let mut restored = crate::SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
restored.load_prefs(&app.prefs());
|
||||
assert_eq!(restored.ui.recent_colors.first(), Some(&[9, 8, 7]));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Shared image-comparison divider and corner chips. Geometry and ink use kit tokens.
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Rect, Sense, Ui, pos2, vec2};
|
||||
|
||||
pub(crate) fn comparison_position(position: f32) -> f32 {
|
||||
@@ -21,7 +21,7 @@ pub(crate) fn comparison_divider(ui: &mut Ui, canvas: Rect, position: &mut f32,
|
||||
}
|
||||
comparison_divider_chrome(ui, canvas, *position);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Slider, ui.is_enabled(), label));
|
||||
scenario::publish_target("render:compare:divider", rail);
|
||||
solidcraft_ui_registry::publish_target("render:compare:divider", rail);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub(crate) fn comparison_divider_chrome(ui: &Ui, canvas: Rect, position: f32) {
|
||||
for offset in [-t.space_6, 0.0, t.space_6] {
|
||||
painter.circle_filled(handle.center() + vec2(0.0, offset), t.space_2, accent.ink);
|
||||
}
|
||||
scenario::publish_rect("render:compare:handle", handle);
|
||||
solidcraft_ui_registry::publish_rect("render:compare:handle", handle);
|
||||
}
|
||||
|
||||
/// Token-painted chip anchored inside either clipped half of the image canvas.
|
||||
@@ -51,5 +51,5 @@ pub(crate) fn comparison_badge(ui: &mut Ui, bounds: Rect, label: &str, right: bo
|
||||
painter.rect_filled(rect, t.control_radius, t.panel);
|
||||
painter.rect_stroke(rect, t.control_radius, t.control_border_stroke(t.control_border), egui::StrokeKind::Inside);
|
||||
painter.with_clip_rect(rect).galley(pos2(rect.left() + t.space_8, rect.center().y - galley.size().y * 0.5), galley, t.form_ink);
|
||||
scenario::publish_rect(if right { "render:compare:right-label" } else { "render:compare:left-label" }, rect);
|
||||
solidcraft_ui_registry::publish_rect(if right { "render:compare:right-label" } else { "render:compare:left-label" }, rect);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Modal confirmations share the Panel/Window frame and the single kit footer.
|
||||
use super::{Action, Footer, Window, actions::footer_with_left, text::paint_text};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Ui, vec2};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -104,7 +104,7 @@ impl<'a> Confirm<'a> {
|
||||
} else {
|
||||
crate::icons::paint_accented(ui.painter(), disc.shrink(t.space_8), self.icon, accent, 1.0);
|
||||
}
|
||||
scenario::publish_rect("confirm:disc", disc);
|
||||
solidcraft_ui_registry::publish_rect("confirm:disc", disc);
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(ui.available_width().max(0.0));
|
||||
let (heading, _) = ui.allocate_exact_size(vec2(ui.available_width().max(0.0), t.form_close_size), egui::Sense::hover());
|
||||
@@ -118,8 +118,8 @@ impl<'a> Confirm<'a> {
|
||||
.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), self.title));
|
||||
let response = super::icon_button_at(ui, close, "close", "Close");
|
||||
closed = response.clicked();
|
||||
scenario::publish_target("dialog:close", response.rect);
|
||||
scenario::publish_rect("dialog:header", heading);
|
||||
solidcraft_ui_registry::publish_target("dialog:close", response.rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:header", heading);
|
||||
message(ui);
|
||||
if let Some(detail) = self.detail {
|
||||
super::list_frame(ui, "confirm_detail", |ui| {
|
||||
|
||||
@@ -583,7 +583,7 @@ impl Container {
|
||||
tip.show(|ui| {
|
||||
let body = |ui: &mut egui::Ui| {
|
||||
ui.label(text);
|
||||
crate::scenario::publish_rect("kit:tooltip", ui.min_rect());
|
||||
solidcraft_ui_registry::publish_rect("kit:tooltip", ui.min_rect());
|
||||
};
|
||||
match slot {
|
||||
Some(slot) => slot.body(ui, body),
|
||||
@@ -607,7 +607,7 @@ impl Container {
|
||||
tip.show(|ui| {
|
||||
let body = |ui: &mut egui::Ui| {
|
||||
add(ui);
|
||||
crate::scenario::publish_rect("kit:tooltip", ui.min_rect());
|
||||
solidcraft_ui_registry::publish_rect("kit:tooltip", ui.min_rect());
|
||||
};
|
||||
match slot {
|
||||
Some(slot) => slot.body(ui, body),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Description-driven details content. No tool, document or command knowledge lives here.
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{fonts::RichTextExt as _, scenario, theme::Tokens};
|
||||
use crate::{fonts::RichTextExt as _, theme::Tokens};
|
||||
use egui::{RichText, Ui};
|
||||
|
||||
const FIELD_FOCUS: &str = "details_field_focus";
|
||||
@@ -201,7 +201,7 @@ fn setting(ui: &mut Ui, field: &DetailSetting, focus: bool) -> Option<DetailEven
|
||||
}
|
||||
};
|
||||
if let Some(response) = response {
|
||||
scenario::publish_target(&format!("details:field:{}", field.id), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("details:field:{}", field.id), response.rect);
|
||||
// Visibility reads Context::will_discard. Never re-enter Context while
|
||||
// holding its data write lock.
|
||||
let visible = ui.is_visible() && ui.is_enabled() && ui.is_rect_visible(response.rect);
|
||||
@@ -235,7 +235,7 @@ fn setting(ui: &mut Ui, field: &DetailSetting, focus: bool) -> Option<DetailEven
|
||||
&& ![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]].contains(vector)
|
||||
{
|
||||
let response = ui.add(egui::Label::new(RichText::new(current).monospace()).wrap()).backing_on_hover_text(current);
|
||||
scenario::publish_target(&format!("details:current:{}", field.id), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("details:current:{}", field.id), response.rect);
|
||||
}
|
||||
changed.then(|| DetailEvent::Change(field.id.clone(), value))
|
||||
}
|
||||
@@ -257,7 +257,7 @@ pub(crate) fn details(ui: &mut Ui, id: &str, description: &Details, max_height:
|
||||
if let Some(caption) = &description.caption {
|
||||
let response = ui.add(egui::Label::new(RichText::new(caption).size(t.font_label).color(t.text_dim)).wrap());
|
||||
caption_height = response.rect.height() + t.space_8;
|
||||
scenario::publish_rect("details:caption", response.rect);
|
||||
solidcraft_ui_registry::publish_rect("details:caption", response.rect);
|
||||
}
|
||||
let footer_height = if description.actions.is_empty() { 0.0 } else { t.control_height + t.space_16 };
|
||||
let body = egui::ScrollArea::vertical()
|
||||
@@ -318,7 +318,7 @@ pub(crate) fn details(ui: &mut Ui, id: &str, description: &Details, max_height:
|
||||
.push_id(&row.id, |ui| super::icon_button_at(ui, rect, "fit", crate::i18n::tr("ui.details_target")))
|
||||
.inner;
|
||||
remember_control(ui, response.id);
|
||||
scenario::publish_target(&format!("details:target:{}", row.id), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("details:target:{}", row.id), response.rect);
|
||||
if response.clicked() {
|
||||
events.push(DetailEvent::Target(row.id.clone()));
|
||||
}
|
||||
@@ -327,7 +327,7 @@ pub(crate) fn details(ui: &mut Ui, id: &str, description: &Details, max_height:
|
||||
});
|
||||
}
|
||||
});
|
||||
scenario::publish_count(
|
||||
solidcraft_ui_registry::publish_count(
|
||||
"details:findings_visible",
|
||||
if expanded { description.findings.len() } else { description.findings.len().min(6) } as f64,
|
||||
);
|
||||
@@ -335,7 +335,7 @@ pub(crate) fn details(ui: &mut Ui, id: &str, description: &Details, max_height:
|
||||
let text = crate::i18n::format("ui.details_show_all", &[("count", &description.findings.len().to_string())]);
|
||||
let response = super::button(ui, text, super::Button::Ghost);
|
||||
remember_control(ui, response.id);
|
||||
scenario::publish_target("details:show_all", response.rect);
|
||||
solidcraft_ui_registry::publish_target("details:show_all", response.rect);
|
||||
if response.clicked() {
|
||||
ui.data_mut(|data| data.insert_temp(expanded_id, true));
|
||||
}
|
||||
@@ -357,7 +357,7 @@ pub(crate) fn details(ui: &mut Ui, id: &str, description: &Details, max_height:
|
||||
});
|
||||
}
|
||||
});
|
||||
scenario::publish_count(
|
||||
solidcraft_ui_registry::publish_count(
|
||||
"details:readout_visible",
|
||||
if expanded { description.readout.len() } else { description.readout.len().min(6) } as f64,
|
||||
);
|
||||
@@ -365,27 +365,27 @@ pub(crate) fn details(ui: &mut Ui, id: &str, description: &Details, max_height:
|
||||
let text = crate::i18n::format("ui.details_show_all", &[("count", &description.readout.len().to_string())]);
|
||||
let response = super::button(ui, text, super::Button::Ghost);
|
||||
remember_control(ui, response.id);
|
||||
scenario::publish_target("details:show_all", response.rect);
|
||||
solidcraft_ui_registry::publish_target("details:show_all", response.rect);
|
||||
if response.clicked() {
|
||||
ui.data_mut(|data| data.insert_temp(expanded_id, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
scenario::publish_rect("details:body", body.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect("details:body", body.inner_rect);
|
||||
if !description.actions.is_empty() {
|
||||
super::divider(ui, "details_actions");
|
||||
let actions = ui.horizontal(|ui| {
|
||||
for (id, label) in description.actions.iter().take(2) {
|
||||
let response = super::text_button(ui, label, true);
|
||||
remember_control(ui, response.id);
|
||||
scenario::publish_target(&format!("details:action:{id}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("details:action:{id}"), response.rect);
|
||||
if response.clicked() {
|
||||
events.push(DetailEvent::Action(id.clone()));
|
||||
}
|
||||
}
|
||||
});
|
||||
scenario::publish_rect("details:actions", actions.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("details:actions", actions.response.rect);
|
||||
}
|
||||
});
|
||||
events
|
||||
|
||||
@@ -4,10 +4,11 @@ use super::{
|
||||
metrics::*,
|
||||
text::paint_text,
|
||||
};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Response, RichText, Ui, vec2};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// One alignment policy for every kit text input. Align the galley itself rather than
|
||||
/// approximating font metrics with integer margins (Plex Mono and Sans differ).
|
||||
@@ -16,6 +17,48 @@ pub(super) fn text_input(edit: egui::TextEdit<'_>) -> egui::TextEdit<'_> {
|
||||
edit.align(egui::Align2::LEFT_CENTER).margin(vec2(t.form_inset, 0.0)).min_size(vec2(0.0, t.control_height))
|
||||
}
|
||||
|
||||
/// The host's expression language, as the kit's value fields use it. The kit draws and edits the
|
||||
/// text; it never parses expressions itself. The host installs one per context with
|
||||
/// [`install_expressions`]. Without one, a field's label drag changes nothing (no text is a
|
||||
/// literal) and an editor offers no completion or stepping.
|
||||
pub trait Expressions: Send + Sync {
|
||||
/// Whether the trimmed `text` is a plain literal: a number with an optional unit ("20 mm",
|
||||
/// "-1.5 in"), not a parameter or a formula. Only a literal can be changed by a label drag.
|
||||
fn is_literal(&self, text: &str) -> bool;
|
||||
/// Whether `unit`, the text after a literal's number as written (" mm", "in", ""), is a unit
|
||||
/// of the language. An empty unit is a unit-less number.
|
||||
fn is_unit(&self, unit: &str) -> bool;
|
||||
/// Offer completion and stepping for this frame under the editor `response` over `text`.
|
||||
/// Returns true when either changed the text; that frame's Enter then accepts the change
|
||||
/// instead of submitting.
|
||||
fn complete(&self, ui: &mut Ui, response: &Response, text: &mut String) -> bool;
|
||||
}
|
||||
|
||||
/// Where a context keeps its installed [`Expressions`].
|
||||
fn expressions_id() -> egui::Id {
|
||||
egui::Id::unique("sc_kit_expressions")
|
||||
}
|
||||
|
||||
/// Give every kit value field in `ctx` the host's expression language. Installing again replaces it.
|
||||
pub fn install_expressions(ctx: &egui::Context, expressions: Arc<dyn Expressions>) {
|
||||
ctx.data_mut(|data| data.insert_temp(expressions_id(), expressions));
|
||||
}
|
||||
|
||||
/// The expression language installed in `ctx`, if any.
|
||||
pub(crate) fn expressions(ctx: &egui::Context) -> Option<Arc<dyn Expressions>> {
|
||||
ctx.data(|data| data.get_temp::<Arc<dyn Expressions>>(expressions_id()))
|
||||
}
|
||||
|
||||
/// A plain number typed into a field, with a decimal point or a decimal comma (#1347): "2.5"
|
||||
/// and "2,5" are both 2.5, as a numeric keypad types either depending on the locale. Spaces are
|
||||
/// ignored and "−" is a minus, as in egui's own parser. A comma beside a point ("1.234,5") or a
|
||||
/// second comma is not guessed at: the text is not a number.
|
||||
pub(crate) fn parse_number(text: &str) -> Option<f64> {
|
||||
let t: String = text.chars().filter(|c| !c.is_whitespace()).map(|c| if c == '\u{2212}' { '-' } else { c }).collect();
|
||||
let t = if !t.contains('.') && t.matches(',').count() == 1 { t.replacen(',', ".", 1) } else { t };
|
||||
t.parse::<f64>().ok().filter(|v| v.is_finite())
|
||||
}
|
||||
|
||||
/// Expression editor with the application's existing completion and number-focus behaviour.
|
||||
/// Values carry their own unit text ("20 mm", "45 deg"): there is no unit suffix. Returns accepted Enter.
|
||||
/// Tokens: `control_height, control_fill, form_inset, font_label, space_4; scope tokens`.
|
||||
@@ -86,7 +129,7 @@ pub(crate) fn count_field(ui: &mut Ui, value: &mut String, limits: &Count<'_>) -
|
||||
));
|
||||
let sign = if delta > 0 { "+1" } else { "−1" };
|
||||
arrow.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, enabled, format!("{} {sign}", crate::i18n::tr_label(&row))));
|
||||
scenario::publish_handle(&format!("dialog:{row} {name}"), half.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{row} {name}"), half.center());
|
||||
if let (true, true, Some(next)) = (enabled, arrow.clicked(), next) {
|
||||
*value = next;
|
||||
}
|
||||
@@ -140,18 +183,20 @@ struct Literal {
|
||||
/// The largest value a label drag reads or writes.
|
||||
const LITERAL_CAP: f64 = 1e12;
|
||||
|
||||
/// `text` as a [`Literal`], or None for anything else: a parameter, a formula, an empty field.
|
||||
/// Dragging never rewrites an expression, so a driving parameter stays driving.
|
||||
fn literal(text: &str) -> Option<Literal> {
|
||||
/// `text` as a [`Literal`] of the `expressions` language, or None for anything else: a parameter,
|
||||
/// a formula, an empty field. Dragging never rewrites an expression, so a driving parameter stays driving.
|
||||
fn literal(expressions: &dyn Expressions, text: &str) -> Option<Literal> {
|
||||
let text = text.trim();
|
||||
if !solidcraft_engine::doc::expr::is_literal(text) {
|
||||
if !expressions.is_literal(text) {
|
||||
return None;
|
||||
}
|
||||
let sign = usize::from(text.starts_with(['-', '+']));
|
||||
let end = text.char_indices().skip(sign).find(|(_, c)| !(c.is_ascii_digit() || *c == '.' || *c == ',')).map_or(text.len(), |(at, _)| at);
|
||||
let (number, unit) = (text.get(..end)?, text.get(end..)?);
|
||||
// An exponent ("1e3") or anything else that is not a unit is left alone.
|
||||
solidcraft_engine::doc::expr::unit_info(unit)?;
|
||||
if !expressions.is_unit(unit) {
|
||||
return None;
|
||||
}
|
||||
let comma = number.contains(',');
|
||||
let number: f64 = number.replace(',', ".").parse().ok().filter(|n: &f64| n.is_finite() && n.abs() < LITERAL_CAP)?;
|
||||
Some(Literal { number, unit: unit.to_owned(), comma })
|
||||
@@ -200,7 +245,7 @@ struct Scrub {
|
||||
/// holding an expression or a parameter is left alone. Returns whether the text changed.
|
||||
pub(super) fn scrub(ui: &Ui, label: &Response, value: &mut String) -> bool {
|
||||
let id = label.id.with("sc_scrub");
|
||||
let Some(now) = literal(value).filter(|_| ui.is_enabled()) else {
|
||||
let Some(now) = expressions(ui.ctx()).and_then(|e| literal(e.as_ref(), value)).filter(|_| ui.is_enabled()) else {
|
||||
ui.ctx().data_mut(|data| data.remove::<Scrub>(id));
|
||||
return false;
|
||||
};
|
||||
@@ -333,7 +378,7 @@ fn searchable_dropdown_with(
|
||||
menu: SearchMenu<'_>,
|
||||
) -> Option<String> {
|
||||
let SearchMenu { custom, pending, icon } = menu;
|
||||
crate::prefs_search::note_current(ui, current);
|
||||
super::settings_search::note_current(ui, current);
|
||||
let mut chosen = None;
|
||||
let row = label(ui).to_owned();
|
||||
let combo = egui::ComboBox::from_id_salt(id)
|
||||
@@ -355,12 +400,12 @@ fn searchable_dropdown_with(
|
||||
);
|
||||
// Preferences types into its own search only while no field has the keyboard.
|
||||
if search.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
scenario::publish_handle(&format!("dialog:{row} search"), search.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{row} search"), search.rect.center());
|
||||
if let Some(custom) = custom {
|
||||
let response = ui.selectable_label(false, custom);
|
||||
scenario::publish_handle(&format!("dialog:{row} file"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{row} file"), response.rect.center());
|
||||
if response.clicked() {
|
||||
chosen = Some(filter.trim().to_owned());
|
||||
ui.close();
|
||||
@@ -377,7 +422,7 @@ fn searchable_dropdown_with(
|
||||
for option in options.iter().filter(|o| o.label.to_lowercase().contains(&query)) {
|
||||
let response = ui.selectable_label(current == option.label, option.label);
|
||||
if let Some(handle) = option.handle {
|
||||
scenario::publish_handle(handle, response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(handle, response.rect.center());
|
||||
}
|
||||
if response.clicked() {
|
||||
chosen = Some(option.value.to_owned());
|
||||
@@ -453,12 +498,12 @@ pub(crate) enum Picked {
|
||||
/// list stays cheap. Returns the pick and the control's response.
|
||||
/// Tokens: dropdown tokens, field tokens, `space_24`, `font_small`, `text_dim`, `form_card_width`.
|
||||
pub(crate) fn search_picker(ui: &mut Ui, p: SearchPicker<'_>) -> egui::InnerResponse<Option<Picked>> {
|
||||
crate::prefs_search::note_current(ui, p.current);
|
||||
super::settings_search::note_current(ui, p.current);
|
||||
// As in `dropdown_menu_with_close_behavior`: only a search probe skips the real control.
|
||||
if crate::prefs_search::querying(ui)
|
||||
&& (!ui.is_visible() || (crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[p.current])))
|
||||
if super::settings_search::querying(ui)
|
||||
&& (!ui.is_visible() || (super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[p.current])))
|
||||
{
|
||||
return egui::InnerResponse::new(None, crate::prefs_search::hidden_response(ui));
|
||||
return egui::InnerResponse::new(None, super::settings_search::hidden_response(ui));
|
||||
}
|
||||
let state = egui::Id::unique(p.id);
|
||||
let mut picked = None;
|
||||
@@ -487,7 +532,7 @@ pub(crate) fn search_picker(ui: &mut Ui, p: SearchPicker<'_>) -> egui::InnerResp
|
||||
})
|
||||
.inner;
|
||||
publish(ui, &response, egui::Role::ComboBox);
|
||||
scenario::publish_target(p.handles, response.rect);
|
||||
solidcraft_ui_registry::publish_target(p.handles, response.rect);
|
||||
egui::InnerResponse::new(picked, response)
|
||||
}
|
||||
|
||||
@@ -513,7 +558,7 @@ fn search_picker_popup(ui: &mut Ui, p: &SearchPicker<'_>, state: egui::Id) -> Op
|
||||
.desired_width(f32::INFINITY),
|
||||
);
|
||||
t.input_bevel(ui, &search);
|
||||
scenario::publish_target(&format!("{}:search", p.handles), search.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:search", p.handles), search.rect);
|
||||
// Asked until it has it: over a modal window egui drops the request of the opening's first frame.
|
||||
if !ui.data(|data| data.get_temp::<bool>(focused_id)).unwrap_or(false) {
|
||||
if search.has_focus() {
|
||||
@@ -524,7 +569,7 @@ fn search_picker_popup(ui: &mut Ui, p: &SearchPicker<'_>, state: egui::Id) -> Op
|
||||
}
|
||||
}
|
||||
if search.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
if search.changed() {
|
||||
ui.data_mut(|data| data.insert_temp(filter_id, filter.clone()));
|
||||
@@ -601,7 +646,7 @@ fn search_picker_popup(ui: &mut Ui, p: &SearchPicker<'_>, state: egui::Id) -> Op
|
||||
search_heading(ui, crate::i18n::tr("ui.agent_chat_custom_heading"), t.space_16);
|
||||
let text = crate::i18n::format("ui.agent_chat_model_custom", &[("model", &compact(model, SEARCH_LABEL_CAP))]);
|
||||
let row = search_row(ui, &text, false);
|
||||
scenario::publish_target(&format!("{}:custom", p.handles), row.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:custom", p.handles), row.rect);
|
||||
if row.clicked() {
|
||||
picked = Some(Picked::Custom(model.to_owned()));
|
||||
}
|
||||
@@ -621,14 +666,14 @@ fn search_picker_popup(ui: &mut Ui, p: &SearchPicker<'_>, state: egui::Id) -> Op
|
||||
SearchRow::Heading(group) => search_heading(ui, group, row_height),
|
||||
SearchRow::Group { name, count, open } => {
|
||||
let response = search_group_row(ui, name, *count, *open);
|
||||
scenario::publish_target(&format!("{}:group:{}", p.handles, name), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:group:{}", p.handles, name), response.rect);
|
||||
if response.clicked() {
|
||||
toggled = Some(((*name).to_owned(), !*open));
|
||||
}
|
||||
}
|
||||
SearchRow::Choice(choice) => {
|
||||
let response = search_row(ui, &compact(choice.label, SEARCH_LABEL_CAP), choice.value == p.current_value);
|
||||
scenario::publish_target(&format!("{}:{}", p.handles, choice.value), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:{}", p.handles, choice.value), response.rect);
|
||||
let response = if choice.tip.is_empty() {
|
||||
response
|
||||
} else {
|
||||
@@ -814,7 +859,7 @@ fn editor_reserving(ui: &mut Ui, value: &mut String, hint: &str, completion: boo
|
||||
}
|
||||
super::text_menu::menu(ui, &mut response, value, super::text_menu::Field::LINE);
|
||||
if response.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
if completion && ui.is_enabled() {
|
||||
ui.ctx().data_mut(|data| {
|
||||
@@ -834,7 +879,7 @@ fn editor_reserving(ui: &mut Ui, value: &mut String, hint: &str, completion: boo
|
||||
});
|
||||
super::layout::publish_geometry(ui, &response);
|
||||
// A completion's Enter accepts the suggestion, rather than submitting the dialog.
|
||||
let completed = completion && crate::params_dialog::complete(ui, &response, value);
|
||||
let completed = completion && expressions(ui.ctx()).is_some_and(|e| e.complete(ui, &response, value));
|
||||
(!completed && response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)), response.changed(), response)
|
||||
}
|
||||
|
||||
@@ -850,7 +895,7 @@ pub(crate) fn dropdown(ui: &mut Ui, id: &str, selected: &mut usize, options: &[&
|
||||
dropdown_menu(ui, id, text, |ui| {
|
||||
for (index, option) in options.iter().enumerate() {
|
||||
let response = ui.selectable_value(selected, index, crate::i18n::tr_label(option));
|
||||
scenario::publish_target(&format!("dialog:{row}:{option}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:{row}:{option}"), response.rect);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -864,7 +909,7 @@ pub(crate) fn dropdown_icons(ui: &mut Ui, id: &str, selected: &mut usize, option
|
||||
for (index, (text, icon)) in options.iter().enumerate() {
|
||||
let response = ui.selectable_label(*selected == index, dropdown_icon_text(text));
|
||||
paint_dropdown_icon(ui, response.rect, icon);
|
||||
scenario::publish_target(&format!("dialog:{row}:{text}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:{row}:{text}"), response.rect);
|
||||
if response.clicked() {
|
||||
*selected = index;
|
||||
}
|
||||
@@ -890,7 +935,7 @@ fn paint_dropdown_icon(ui: &Ui, rect: egui::Rect, icon: &str) {
|
||||
vec2(t.space_16, t.space_16),
|
||||
);
|
||||
crate::icons::paint_ink(ui.painter(), rect, icon, t.form_ink);
|
||||
scenario::publish_rect(&format!("dialog:dropdown_icon:{icon}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:dropdown_icon:{icon}"), rect);
|
||||
}
|
||||
|
||||
/// [`dropdown`] whose options each show a picture beside them while hovered, painted by `preview`
|
||||
@@ -902,10 +947,10 @@ pub(crate) fn dropdown_previews(ui: &mut Ui, id: &str, selected: &mut usize, opt
|
||||
dropdown_menu(ui, id, text, |ui| {
|
||||
for (index, option) in options.iter().enumerate() {
|
||||
let response = ui.selectable_value(selected, index, crate::i18n::tr_label(option));
|
||||
scenario::publish_target(&format!("dialog:{row}:{option}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:{row}:{option}"), response.rect);
|
||||
Container::tooltip_ui(&response, |ui| {
|
||||
preview(ui, index);
|
||||
scenario::publish_rect(&format!("dialog:preview:{row}:{option}"), ui.min_rect());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:preview:{row}:{option}"), ui.min_rect());
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -921,7 +966,7 @@ pub(crate) fn dropdown_picked(ui: &mut Ui, id: &str, selected: &mut usize, optio
|
||||
for (index, option) in options.iter().enumerate() {
|
||||
let response = ui.selectable_value(selected, index, crate::i18n::tr_label(option));
|
||||
picked |= response.clicked();
|
||||
scenario::publish_handle(&format!("dialog:{row}:{option}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{row}:{option}"), response.rect.center());
|
||||
}
|
||||
});
|
||||
picked
|
||||
@@ -936,11 +981,11 @@ pub(crate) fn dropdown_menu(ui: &mut Ui, id: impl egui::AsIdSalt, current: &str,
|
||||
/// A width-bounded toolbar picker. Long selected values and popup rows truncate;
|
||||
/// callers can retain their complete labels in hover text.
|
||||
pub(crate) fn bounded_dropdown_menu(ui: &mut Ui, id: impl egui::AsIdSalt, current: &str, width: f32, add: impl FnOnce(&mut Ui)) -> Response {
|
||||
crate::prefs_search::note_current(ui, current);
|
||||
if crate::prefs_search::querying(ui)
|
||||
&& (!ui.is_visible() || (crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[current])))
|
||||
super::settings_search::note_current(ui, current);
|
||||
if super::settings_search::querying(ui)
|
||||
&& (!ui.is_visible() || (super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[current])))
|
||||
{
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let width = dimension(width);
|
||||
let response = ui
|
||||
@@ -1005,13 +1050,13 @@ fn dropdown_menu_width(
|
||||
close_behavior: egui::PopupCloseBehavior,
|
||||
add: impl FnOnce(&mut Ui),
|
||||
) -> Response {
|
||||
crate::prefs_search::note_current(ui, current);
|
||||
super::settings_search::note_current(ui, current);
|
||||
// Only a search probe may skip the real control. A dialog's first frame is not visible yet,
|
||||
// and a stand-in there makes the next frame too short to show OK.
|
||||
if crate::prefs_search::querying(ui)
|
||||
&& (!ui.is_visible() || (crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[current])))
|
||||
if super::settings_search::querying(ui)
|
||||
&& (!ui.is_visible() || (super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[current])))
|
||||
{
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let width = if compact {
|
||||
@@ -1106,9 +1151,9 @@ pub(crate) fn selection_field(ui: &mut Ui, text: &str, options: Selection) -> Re
|
||||
false,
|
||||
);
|
||||
publish(ui, &response, egui::Role::Button);
|
||||
scenario::publish_rect(&format!("dialog:field:{}", label(ui)), rect);
|
||||
scenario::publish_count(&format!("dialog:selection:{}:filled", label(ui)), f64::from(filled));
|
||||
scenario::publish_count(&format!("dialog:selection:{}:active", label(ui)), f64::from(active));
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:field:{}", label(ui)), rect);
|
||||
solidcraft_ui_registry::publish_count(&format!("dialog:selection:{}:filled", label(ui)), f64::from(filled));
|
||||
solidcraft_ui_registry::publish_count(&format!("dialog:selection:{}:active", label(ui)), f64::from(active));
|
||||
response
|
||||
}
|
||||
|
||||
@@ -1158,8 +1203,8 @@ pub(crate) fn number_pair<T: egui::emath::Numeric>(
|
||||
/// Retains the established 0.01 step, fractional range and locale parser.
|
||||
/// Locale-aware variable-radius position editor. Tokens: `scope control/font tokens`.
|
||||
pub(crate) fn fraction_field(ui: &mut Ui, value: &mut f64) -> Response {
|
||||
let response = ui.add(egui::DragValue::new(value).speed(0.01).range(0.01..=0.99).fixed_decimals(2).custom_parser(crate::prefs::parse_number));
|
||||
scenario::publish_target(&format!("dialog:fraction:{}", label(ui)), response.rect);
|
||||
let response = ui.add(egui::DragValue::new(value).speed(0.01).range(0.01..=0.99).fixed_decimals(2).custom_parser(parse_number));
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:fraction:{}", label(ui)), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -1331,7 +1376,7 @@ pub(crate) fn search_field(ui: &mut Ui, id: egui::Id, value: &mut String, hint:
|
||||
info
|
||||
});
|
||||
if response.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
crate::theme::TabAccent::number_focus(ui, &response);
|
||||
t.input_bevel(ui, &response);
|
||||
@@ -1424,8 +1469,8 @@ impl Default for Slider {
|
||||
/// Native input, keyboard routing, enabled state and accessibility; one token-painted track/thumb.
|
||||
/// Tokens: control_height, space_24/8/4/2, dialog_rule, control_fill, form_primary.
|
||||
pub(crate) fn slider<T: egui::emath::Numeric>(ui: &mut Ui, value: &mut T, range: std::ops::RangeInclusive<T>, options: Slider) -> Response {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
// Own form styling even in callers such as the viewport menu which have no kit scope.
|
||||
super::scope(ui, |ui| {
|
||||
@@ -1513,7 +1558,7 @@ pub(crate) fn slider<T: egui::emath::Numeric>(ui: &mut Ui, value: &mut T, range:
|
||||
ui.ctx().read_response(id).is_some_and(|focused| response.rect.contains(focused.rect.center()))
|
||||
|| egui::TextEdit::load_state(ui.ctx(), id).is_some()
|
||||
}) {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
// Match egui's native input range (handle_radius = thickness / 2.5), so the
|
||||
// painted thumb stays under the pointer even at the two ends of the range.
|
||||
@@ -1534,8 +1579,8 @@ pub(crate) fn slider<T: egui::emath::Numeric>(ui: &mut Ui, value: &mut T, range:
|
||||
ui.painter().circle(center, t.space_8, fill, egui::Stroke::new(t.space_2, if off { t.form_dim } else { primary }));
|
||||
publish(ui, &response, egui::Role::Slider);
|
||||
response.widget_info(|| egui::WidgetInfo::slider(ui.is_enabled(), value.to_f64(), crate::i18n::tr_label(label(ui))));
|
||||
scenario::publish_rect(&format!("dialog:slider_track:{}", label(ui)), slider);
|
||||
scenario::publish_target(&format!("dialog:slider:{}", label(ui)), response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:slider_track:{}", label(ui)), slider);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:slider:{}", label(ui)), response.rect);
|
||||
response
|
||||
})
|
||||
}
|
||||
@@ -1581,7 +1626,7 @@ fn scalar_field_impl(ui: &mut Ui, value: &mut f64, range: std::ops::RangeInclusi
|
||||
let before = *value;
|
||||
let parsed = std::cell::Cell::new(false);
|
||||
let field = egui::DragValue::new(value).range(range).speed(0.01).custom_parser(|text| {
|
||||
let value = crate::prefs::parse_number(text);
|
||||
let value = parse_number(text);
|
||||
parsed.set(value.is_some());
|
||||
value
|
||||
});
|
||||
@@ -1602,7 +1647,7 @@ fn scalar_field_impl(ui: &mut Ui, value: &mut f64, range: std::ops::RangeInclusi
|
||||
if !plain {
|
||||
Tokens::get().input_bevel(ui, &response);
|
||||
}
|
||||
scenario::publish_target(&format!("dialog:scalar:{}", label(ui)), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:scalar:{}", label(ui)), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -1611,7 +1656,7 @@ fn scalar_field_impl(ui: &mut Ui, value: &mut f64, range: std::ops::RangeInclusi
|
||||
pub(crate) fn length_field(ui: &mut Ui, value: &mut f64, units: &str, decimals: u8) -> Response {
|
||||
let response = ui.add(length_drag(value, units, decimals));
|
||||
Tokens::get().input_bevel(ui, &response);
|
||||
scenario::publish_target(&format!("dialog:length:{}", label(ui)), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:length:{}", label(ui)), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -1625,7 +1670,7 @@ pub(crate) fn bounded_length_field(
|
||||
speed: f64,
|
||||
) -> Response {
|
||||
let response = ui.add(length_drag(value, units, decimals).range(range).speed(speed));
|
||||
scenario::publish_target(&format!("dialog:length:{}", label(ui)), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:length:{}", label(ui)), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -1640,13 +1685,13 @@ pub(crate) fn angle_field(ui: &mut Ui, value: &mut f64) -> Response {
|
||||
if let Some(last) = ui.data(|d| d.get_temp::<f64>(set)).filter(|last| (last - shown).abs() <= 1e-9 * last.abs().max(1.0)) {
|
||||
shown = last;
|
||||
}
|
||||
let response = ui.add(egui::DragValue::new(&mut shown).speed(1.0).suffix(" deg").custom_parser(crate::prefs::parse_number));
|
||||
let response = ui.add(egui::DragValue::new(&mut shown).speed(1.0).suffix(" deg").custom_parser(parse_number));
|
||||
if response.changed() {
|
||||
*value = shown;
|
||||
ui.data_mut(|d| d.insert_temp(set, shown));
|
||||
}
|
||||
Tokens::get().input_bevel(ui, &response);
|
||||
scenario::publish_target(&format!("dialog:angle:{}", label(ui)), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:angle:{}", label(ui)), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -1725,15 +1770,10 @@ fn bounded_number_field<T: egui::emath::Numeric>(
|
||||
clamp_existing: bool,
|
||||
) -> Response {
|
||||
let response = ui.add(
|
||||
egui::DragValue::new(value)
|
||||
.range(range)
|
||||
.clamp_existing_to_range(clamp_existing)
|
||||
.speed(speed)
|
||||
.suffix(suffix)
|
||||
.custom_parser(crate::prefs::parse_number),
|
||||
egui::DragValue::new(value).range(range).clamp_existing_to_range(clamp_existing).speed(speed).suffix(suffix).custom_parser(parse_number),
|
||||
);
|
||||
if response.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
Tokens::get().input_bevel(ui, &response);
|
||||
response.widget_info(|| {
|
||||
@@ -1758,7 +1798,7 @@ pub(crate) fn vector3_field(ui: &mut Ui, values: &mut [f64; 3], range: std::ops:
|
||||
ui.spacing_mut().interact_size.x = width;
|
||||
ui.set_clip_rect(ui.clip_rect().intersect(ui.max_rect()));
|
||||
let response = number_field(ui, value, range.clone(), 0.01, suffix);
|
||||
scenario::publish_target(&format!("dialog:vector:{label}:{axis}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:vector:{label}:{axis}"), response.rect);
|
||||
changed |= response.changed();
|
||||
}
|
||||
});
|
||||
@@ -1804,7 +1844,7 @@ pub(crate) fn track_number(ui: &mut Ui, value: &mut f64, speed: f64, decimals: u
|
||||
response
|
||||
}
|
||||
|
||||
/// A value typed as a sum of numbers (`crate::prefs::parse_number` each), added to `now` when it
|
||||
/// A value typed as a sum of numbers (`parse_number` each), added to `now` when it
|
||||
/// starts with a sign: "+30" moves the value by 30, "0-5" is −5.
|
||||
fn relative_number(text: &str, now: f64) -> Option<f64> {
|
||||
let text: String = text.chars().filter(|c| !c.is_whitespace()).map(|c| if c == '\u{2212}' { '-' } else { c }).collect();
|
||||
@@ -1813,13 +1853,13 @@ fn relative_number(text: &str, now: f64) -> Option<f64> {
|
||||
let mut prev = ' ';
|
||||
for c in text.chars() {
|
||||
if (c == '+' || c == '-') && !term.is_empty() && !matches!(prev, 'e' | 'E') {
|
||||
total += crate::prefs::parse_number(&term)?;
|
||||
total += parse_number(&term)?;
|
||||
term.clear();
|
||||
}
|
||||
term.push(c);
|
||||
prev = c;
|
||||
}
|
||||
total += crate::prefs::parse_number(&term)?;
|
||||
total += parse_number(&term)?;
|
||||
total.is_finite().then_some(total)
|
||||
}
|
||||
|
||||
@@ -1931,8 +1971,8 @@ pub(crate) fn library_cell(ui: &mut Ui, id: egui::Id, value: &mut String, hint:
|
||||
let button = egui::Rect::from_min_max(egui::pos2(split, rect.top()), rect.max);
|
||||
t.secondary_vline(ui.painter(), split, rect.y_range(), t.dialog_rule);
|
||||
let library = super::icon_button_at(ui, button, "chevron", "Library");
|
||||
scenario::publish_handle("dialog:menu:Library", button.center());
|
||||
scenario::publish_rect("dialog:menu:Library", button);
|
||||
solidcraft_ui_registry::publish_handle("dialog:menu:Library", button.center());
|
||||
solidcraft_ui_registry::publish_rect("dialog:menu:Library", button);
|
||||
let popup = Container::new(ui.ctx(), Surface::Popup).menu(&library);
|
||||
let open = popup.is_open();
|
||||
popup.show(|ui| super::scope(ui, menu));
|
||||
|
||||
@@ -1419,7 +1419,7 @@ fn a_track_number_adds_up_and_a_sign_adds_to_the_value_now() {
|
||||
|
||||
#[test]
|
||||
fn a_label_drag_reads_only_literal_values_and_moves_them_on_the_step_grid() {
|
||||
let literal = |text: &str| super::literal(text).map(|l| (l.number, l.unit, l.comma));
|
||||
let literal = |text: &str| super::literal(&crate::params_dialog::DocExpressions, text).map(|l| (l.number, l.unit, l.comma));
|
||||
assert_eq!(literal("20 mm"), Some((20.0, " mm".into(), false)));
|
||||
assert_eq!(literal(" 1,5in "), Some((1.5, "in".into(), true)));
|
||||
assert_eq!(literal("-3"), Some((-3.0, String::new(), false)));
|
||||
@@ -1435,7 +1435,7 @@ fn a_label_drag_reads_only_literal_values_and_moves_them_on_the_step_grid() {
|
||||
assert_eq!(super::scrubbed(2.0, -500.0, 0.1), 0.0, "a size stops at zero");
|
||||
assert!((super::scrubbed(-2.0, -10.0, 0.1) + 3.0).abs() < 1e-9, "a negative offset keeps moving");
|
||||
assert_eq!(super::scrubbed(1.0, f32::MAX, 1e8), 1.0, "an overflowing drag keeps the value");
|
||||
let text = |n, step, typed: &str| super::scrub_text(n, step, &super::literal(typed).unwrap());
|
||||
let text = |n, step, typed: &str| super::scrub_text(n, step, &super::literal(&crate::params_dialog::DocExpressions, typed).unwrap());
|
||||
assert_eq!(text(23.0, 0.1, "20 mm"), "23.0 mm");
|
||||
assert_eq!(text(1.75, 0.01, "1,5 in"), "1,75 in");
|
||||
assert_eq!(text(-0.0001, 0.01, "-1"), "0.00");
|
||||
@@ -1473,6 +1473,7 @@ fn dragging_a_value_rows_label_changes_its_literal_and_leaves_expressions_alone(
|
||||
frame(ctx, vec![], modifiers, values);
|
||||
}
|
||||
let ctx = crate::fonts::test_context();
|
||||
crate::params_dialog::install_expressions(&ctx);
|
||||
let mut values = ["20 mm".to_owned(), "depth + 2 mm".to_owned()];
|
||||
frame(&ctx, vec![], Default::default(), &mut values);
|
||||
drag(&ctx, "Distance", 40.0, Default::default(), &mut values);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Shared aspect choices and camera composition guides; no document or command authority.
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Rect, Stroke, Ui, pos2, vec2};
|
||||
|
||||
pub(crate) struct AspectTile<'a> {
|
||||
@@ -48,7 +48,7 @@ pub(crate) fn aspect_grid(ui: &mut Ui, tiles: &[AspectTile<'_>]) -> Option<usize
|
||||
ink,
|
||||
);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), tile.selected, tile.label));
|
||||
scenario::publish_target(tile.id, rect);
|
||||
solidcraft_ui_registry::publish_target(tile.id, rect);
|
||||
if response.clicked() {
|
||||
clicked = Some(row * 3 + column);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Table-driven layout/paint references in both themes. No window or GPU required.
|
||||
use super::preferences_figures::{ThemeSample, paint_theme_thumbnail};
|
||||
use super::*;
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Ui, vec2};
|
||||
use solidcraft_engine::kernel::Continuity;
|
||||
|
||||
@@ -29,7 +29,7 @@ macro_rules! element {
|
||||
};
|
||||
}
|
||||
|
||||
fn navigation_sample(ui: &mut Ui, size: crate::navigation_bar::Size) {
|
||||
fn navigation_sample(ui: &mut Ui, size: NavigationSize) {
|
||||
let metrics = NavigationMetrics::for_size(size);
|
||||
let inset = NAV_PADDING + NAV_BORDER;
|
||||
let menu_width = metrics.icon + NAV_MENU_LEFT + NAV_MENU_RIGHT + NAV_MENU_GAP + NAV_CHEVRON;
|
||||
@@ -38,16 +38,12 @@ fn navigation_sample(ui: &mut Ui, size: crate::navigation_bar::Size) {
|
||||
navigation_surface(ui, rect, size, true);
|
||||
let mut x = rect.left() + inset;
|
||||
for (id, icon, kind, selected, width) in [
|
||||
("pan", "pan", crate::navigation_bar::Kind::Tool, true, metrics.button),
|
||||
("fit", "fit", crate::navigation_bar::Kind::Action, false, metrics.button),
|
||||
("visual_style", "style_shaded_edges", crate::navigation_bar::Kind::Menu, false, menu_width),
|
||||
("pan", "pan", NavigationKind::Tool, true, metrics.button),
|
||||
("fit", "fit", NavigationKind::Action, false, metrics.button),
|
||||
("visual_style", "style_shaded_edges", NavigationKind::Menu, false, menu_width),
|
||||
] {
|
||||
let bounds = egui::Rect::from_min_size(egui::pos2(x, rect.top() + inset), vec2(width, metrics.button));
|
||||
navigation_button(
|
||||
ui,
|
||||
bounds,
|
||||
NavigationButton { id, icon, label: id, kind, selected, labels: false, menu: kind == crate::navigation_bar::Kind::Menu },
|
||||
);
|
||||
navigation_button(ui, bounds, NavigationButton { id, icon, label: id, kind, selected, labels: false, menu: kind == NavigationKind::Menu });
|
||||
if id == "fit" {
|
||||
navigation_edit_outline(ui, bounds, true, false);
|
||||
}
|
||||
@@ -220,8 +216,8 @@ const ELEMENTS: &[Element] = &[
|
||||
element!("look_chain_empty", |ui, _| {
|
||||
look_chain(ui, "gallery:empty-chain", ["Face", "Body", "Component", "Material"], [None; 4], LookStep::Face);
|
||||
}),
|
||||
element!("navigation_regular", |ui, _| navigation_sample(ui, crate::navigation_bar::Size::Regular)),
|
||||
element!("navigation_compact", |ui, _| navigation_sample(ui, crate::navigation_bar::Size::Compact)),
|
||||
element!("navigation_regular", |ui, _| navigation_sample(ui, NavigationSize::Regular)),
|
||||
element!("navigation_compact", |ui, _| navigation_sample(ui, NavigationSize::Compact)),
|
||||
element!("navigation_popup", |ui, _| {
|
||||
navigation_menu(ui, NAV_POPUP_WIDTH, |ui| {
|
||||
navigation_menu_heading(ui, "Visual style");
|
||||
@@ -512,7 +508,7 @@ const ELEMENTS: &[Element] = &[
|
||||
element!("catalog_footer_height", |ui, _| {
|
||||
let expected = catalog_footer_height(ui);
|
||||
catalog_footer(ui, "Click to apply to the selection.");
|
||||
assert_eq!(crate::scenario::rect_of("catalog:footer").unwrap().height(), expected);
|
||||
assert_eq!(solidcraft_ui_registry::rect_of("catalog:footer").unwrap().height(), expected);
|
||||
}),
|
||||
element!("catalog_footer_with_tip", |ui, _| {
|
||||
catalog_footer_with_tip(ui, "Click to apply to the selection.", "Paint and drag assignment are not supported yet.");
|
||||
@@ -1216,7 +1212,7 @@ const ELEMENTS: &[Element] = &[
|
||||
let (r, _) = ui.allocate_exact_size(vec2(360.0, 360.0), egui::Sense::hover());
|
||||
let mut app = crate::test_support::sample_app();
|
||||
app.ui.tab = "SURFACE".into();
|
||||
crate::theme::TabAccent::publish(&app, ui.ctx());
|
||||
app.publish_tab_accent(ui.ctx());
|
||||
radial_menu(ui, r.center(), &crate::context_menu::radial_items(&app), Some(3));
|
||||
}),
|
||||
element!("workspace_menu", |ui, _| {
|
||||
@@ -1922,7 +1918,7 @@ const ELEMENTS: &[Element] = &[
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(t.form_button_height * 3.0, t.form_button_height * 2.0), egui::Sense::click());
|
||||
ui.painter().add(persistent_button_shape(ui, &response, rect));
|
||||
ui.painter().text(rect.center(), egui::Align2::CENTER_CENTER, "Selected", crate::fonts::semibold(t.font_label), t.selected_ink);
|
||||
scenario::publish_target("gallery:persistent_button", rect);
|
||||
solidcraft_ui_registry::publish_target("gallery:persistent_button", rect);
|
||||
}),
|
||||
element!("native_button_focus", "gallery:native_button_focus", Some("gallery:native_button_focus"), |ui, _| {
|
||||
let t = Tokens::get();
|
||||
@@ -1930,7 +1926,7 @@ const ELEMENTS: &[Element] = &[
|
||||
response.request_focus();
|
||||
ui.painter().rect_filled(rect, t.radius_sm, t.control_fill);
|
||||
ui.painter().add(native_button_focus(ui, &response, rect));
|
||||
scenario::publish_target("gallery:native_button_focus", rect);
|
||||
solidcraft_ui_registry::publish_target("gallery:native_button_focus", rect);
|
||||
}),
|
||||
element!("button", "dialog:row:Gallery", Some("dialog:button:Gallery:Add"), |ui, _| {
|
||||
row(ui, "Gallery", |ui| {
|
||||
@@ -2094,7 +2090,7 @@ fn example(ui: &mut Ui, element: &Element) -> (&'static str, Option<&'static str
|
||||
// actual shadow bounds as part of the gallery sample, keeping the paint guard strict.
|
||||
ui.expand_to_include_rect(rect + Menu::frame().shadow.margin());
|
||||
}
|
||||
scenario::publish_rect("gallery:allocation", rect);
|
||||
solidcraft_ui_registry::publish_rect("gallery:allocation", rect);
|
||||
(element.rect, element.handle)
|
||||
}
|
||||
|
||||
@@ -2109,11 +2105,11 @@ fn every_element_has_geometry_and_stays_in_its_allocation_in_both_themes() {
|
||||
ctx.set_visuals(if dark { egui::Visuals::dark() } else { egui::Visuals::light() });
|
||||
for pass in 0..3 {
|
||||
let events = if element.name.starts_with("checkbox_hovered") && pass > 0 {
|
||||
vec![egui::Event::PointerMoved(scenario::rect_of("gallery:allocation").expect("hover allocation").center())]
|
||||
vec![egui::Event::PointerMoved(solidcraft_ui_registry::rect_of("gallery:allocation").expect("hover allocation").center())]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
scenario::clear_handles();
|
||||
solidcraft_ui_registry::clear_handles();
|
||||
let mut allocation = egui::Rect::NOTHING;
|
||||
let mut published = ("", None);
|
||||
let mut output = ctx.run_ui(
|
||||
@@ -2135,12 +2131,12 @@ fn every_element_has_geometry_and_stays_in_its_allocation_in_both_themes() {
|
||||
},
|
||||
);
|
||||
output.textures_delta.clear();
|
||||
let rect = scenario::rect_of(published.0).unwrap_or_else(|| panic!("{name}: missing {}", published.0));
|
||||
let rect = solidcraft_ui_registry::rect_of(published.0).unwrap_or_else(|| panic!("{name}: missing {}", published.0));
|
||||
assert!(rect.is_finite() && rect.is_positive(), "{name}: {rect:?}");
|
||||
assert!(allocation.expand(1.0).contains_rect(rect), "{name}: {rect:?} outside {allocation:?}");
|
||||
if let Some(handle) = published.1 {
|
||||
assert!(
|
||||
rect.contains(scenario::handle_at(handle).unwrap_or_else(|| panic!("{name}: missing {handle}"))),
|
||||
rect.contains(solidcraft_ui_registry::handle_at(handle).unwrap_or_else(|| panic!("{name}: missing {handle}"))),
|
||||
"{name}: handle outside {rect:?}"
|
||||
);
|
||||
}
|
||||
@@ -2180,26 +2176,27 @@ fn visual_style_dropdown_specimen_selects_real_options_and_paints_their_icons()
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
}
|
||||
for (index, (_, label)) in crate::navigation_bar::VISUAL_STYLES.into_iter().enumerate() {
|
||||
let trigger = scenario::handle_at("dialog:Visual style").unwrap();
|
||||
let trigger = solidcraft_ui_registry::handle_at("dialog:Visual style").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(trigger, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(trigger, false), &mut draw);
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
for (option, (_, name)) in crate::navigation_bar::VISUAL_STYLES.into_iter().enumerate() {
|
||||
let rect = scenario::rect_of(&format!("dialog:Visual style:{name}")).unwrap();
|
||||
let rect = solidcraft_ui_registry::rect_of(&format!("dialog:Visual style:{name}")).unwrap();
|
||||
let icon = crate::navigation_bar::visual_style_icon(option as u8);
|
||||
let painted = scenario::rect_of(&format!("dialog:dropdown_icon:{icon}")).unwrap();
|
||||
let owner = if option == sample.borrow().selected { scenario::rect_of("dialog:widget:Visual style").unwrap() } else { rect };
|
||||
let painted = solidcraft_ui_registry::rect_of(&format!("dialog:dropdown_icon:{icon}")).unwrap();
|
||||
let owner =
|
||||
if option == sample.borrow().selected { solidcraft_ui_registry::rect_of("dialog:widget:Visual style").unwrap() } else { rect };
|
||||
assert!(owner.contains_rect(painted), "{name}: its real icon is in its option or selected trigger");
|
||||
}
|
||||
let option = scenario::handle_at(&format!("dialog:Visual style:{label}")).unwrap();
|
||||
let option = solidcraft_ui_registry::handle_at(&format!("dialog:Visual style:{label}")).unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(option, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(option, false), &mut draw);
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
assert_eq!(sample.borrow().selected, index, "the specimen edits the real selection index");
|
||||
let icon = crate::navigation_bar::visual_style_icon(index as u8);
|
||||
let trigger = scenario::rect_of("dialog:widget:Visual style").unwrap();
|
||||
assert!(trigger.contains_rect(scenario::rect_of(&format!("dialog:dropdown_icon:{icon}")).unwrap()));
|
||||
assert!(scenario::handle_at("dialog:Visual style:Wireframe").is_none(), "the native popup closes after selection");
|
||||
let trigger = solidcraft_ui_registry::rect_of("dialog:widget:Visual style").unwrap();
|
||||
assert!(trigger.contains_rect(solidcraft_ui_registry::rect_of(&format!("dialog:dropdown_icon:{icon}")).unwrap()));
|
||||
assert!(solidcraft_ui_registry::handle_at("dialog:Visual style:Wireframe").is_none(), "the native popup closes after selection");
|
||||
}
|
||||
}
|
||||
crate::theme::set_dark(previous);
|
||||
@@ -2214,7 +2211,7 @@ fn apply_does_not_submit_and_obeys_validation() {
|
||||
action = footer(ui, "OK", "Cancel", enabled, Footer { apply: Some("Apply"), ..Default::default() });
|
||||
};
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
let at = scenario::handle_at("dialog:apply").unwrap();
|
||||
let at = solidcraft_ui_registry::handle_at("dialog:apply").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, false), &mut draw);
|
||||
assert_eq!(action, if enabled { Action::Apply } else { Action::None });
|
||||
@@ -2229,7 +2226,7 @@ fn picked_item_remove_and_paired_values_have_independent_targets() {
|
||||
removed = list_row(ui, "point-1", "Point 1").clicked();
|
||||
};
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
let at = scenario::handle_at("dialog:list:point-1:remove").unwrap();
|
||||
let at = solidcraft_ui_registry::handle_at("dialog:list:point-1:remove").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, false), &mut draw);
|
||||
assert!(removed);
|
||||
@@ -2240,9 +2237,9 @@ fn picked_item_remove_and_paired_values_have_independent_targets() {
|
||||
value_pair(ui, "Position", [&mut x, &mut y], ["X", "Y"], None);
|
||||
};
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
let row = scenario::rect_of("dialog:row:Position").unwrap();
|
||||
let first = scenario::handle_at("dialog:X").unwrap();
|
||||
let second = scenario::handle_at("dialog:Y").unwrap();
|
||||
let row = solidcraft_ui_registry::rect_of("dialog:row:Position").unwrap();
|
||||
let first = solidcraft_ui_registry::handle_at("dialog:X").unwrap();
|
||||
let second = solidcraft_ui_registry::handle_at("dialog:Y").unwrap();
|
||||
assert_eq!(row.height(), Tokens::get().control_height);
|
||||
assert!(row.contains(first) && row.contains(second) && first.x < second.x);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(second, true), &mut draw);
|
||||
@@ -2269,12 +2266,12 @@ fn gallery_rows_share_a_label_column_after_measurement_in_both_themes() {
|
||||
});
|
||||
});
|
||||
}
|
||||
let first = scenario::rect_of("dialog:control:Edges").unwrap();
|
||||
let second = scenario::rect_of("dialog:control:Counterbore depth").unwrap();
|
||||
let first = solidcraft_ui_registry::rect_of("dialog:control:Edges").unwrap();
|
||||
let second = solidcraft_ui_registry::rect_of("dialog:control:Counterbore depth").unwrap();
|
||||
assert_eq!(first.left(), second.left());
|
||||
assert_eq!(first.height(), Tokens::get().control_height);
|
||||
assert_eq!(second.height(), Tokens::get().control_height);
|
||||
let width = scenario::rect_of("dialog:label:Edges").unwrap().width();
|
||||
let width = solidcraft_ui_registry::rect_of("dialog:label:Edges").unwrap().width();
|
||||
assert!(width >= Tokens::get().form_label_width && width <= Tokens::get().form_label_width * 1.6);
|
||||
}
|
||||
crate::theme::set_dark(previous);
|
||||
@@ -2295,14 +2292,14 @@ fn compound_row_actions_preserve_the_original_field_handle() {
|
||||
field(ui, &mut value);
|
||||
},
|
||||
);
|
||||
expected = scenario::handle_at("dialog:Mid Radius");
|
||||
expected = solidcraft_ui_registry::handle_at("dialog:Mid Radius");
|
||||
button(ui, "×", Button::Small);
|
||||
});
|
||||
});
|
||||
});
|
||||
assert_eq!(scenario::handle_at("dialog:Mid Radius"), expected);
|
||||
assert_eq!(solidcraft_ui_registry::handle_at("dialog:Mid Radius"), expected);
|
||||
assert!(expected.is_some());
|
||||
assert_ne!(scenario::handle_at("dialog:button:Mid Radius:×"), expected);
|
||||
assert_ne!(solidcraft_ui_registry::handle_at("dialog:button:Mid Radius:×"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2326,7 +2323,7 @@ fn emphasis_and_compact_modes_keep_independent_actions_in_both_themes() {
|
||||
};
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
for handle in ["dialog:Bold", "dialog:Italic", "dialog:Alignment:Long translated centre"] {
|
||||
let at = scenario::handle_at(handle).unwrap();
|
||||
let at = solidcraft_ui_registry::handle_at(handle).unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, false), &mut draw);
|
||||
}
|
||||
@@ -2359,8 +2356,8 @@ fn table_cells_keep_alignment_fixed_rows_editing_and_action_targets() {
|
||||
});
|
||||
row.cell(|ui| {
|
||||
let response = cell_editor(ui, ui.scope_id().with("expression"), value, "Expression", false);
|
||||
scenario::publish_handle(&format!("gallery:cell:{name}"), response.rect.center());
|
||||
scenario::publish_rect(&format!("gallery:cell:{name}"), response.rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("gallery:cell:{name}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("gallery:cell:{name}"), response.rect);
|
||||
});
|
||||
row.cell(|ui| {
|
||||
if button(ui, "Delete", Button::Small).clicked() {
|
||||
@@ -2376,18 +2373,18 @@ fn table_cells_keep_alignment_fixed_rows_editing_and_action_targets() {
|
||||
for _ in 0..3 {
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
}
|
||||
let first_rect = scenario::rect_of("gallery:cell:Width").unwrap();
|
||||
let second_rect = scenario::rect_of("gallery:cell:Height").unwrap();
|
||||
let header = scenario::rect_of("dialog:column:cells:Expression").unwrap();
|
||||
let first_rect = solidcraft_ui_registry::rect_of("gallery:cell:Width").unwrap();
|
||||
let second_rect = solidcraft_ui_registry::rect_of("gallery:cell:Height").unwrap();
|
||||
let header = solidcraft_ui_registry::rect_of("dialog:column:cells:Expression").unwrap();
|
||||
assert_eq!(first_rect.left(), header.left());
|
||||
assert_eq!(second_rect.left(), header.left());
|
||||
assert_eq!(first_rect.height(), Tokens::get().control_height);
|
||||
assert_eq!(second_rect.height(), Tokens::get().control_height);
|
||||
let at = scenario::handle_at("gallery:cell:Height").unwrap();
|
||||
let at = solidcraft_ui_registry::handle_at("gallery:cell:Height").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, false), &mut draw);
|
||||
super::test_support::frame(&ctx, vec![egui::Event::Text("x".into())], &mut draw);
|
||||
let delete = scenario::handle_at("dialog:button:Actions:Delete").unwrap();
|
||||
let delete = solidcraft_ui_registry::handle_at("dialog:button:Actions:Delete").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(delete, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(delete, false), &mut draw);
|
||||
assert_eq!(first, "10 mm");
|
||||
@@ -2418,7 +2415,7 @@ fn popup_suggestions_and_selectable_lists_keep_click_selection() {
|
||||
for _ in 0..3 {
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
}
|
||||
let at = scenario::handle_at("dialog:suggestion:1").unwrap();
|
||||
let at = solidcraft_ui_registry::handle_at("dialog:suggestion:1").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, false), &mut draw);
|
||||
assert_eq!(selected, Some(1));
|
||||
@@ -2431,7 +2428,7 @@ fn popup_suggestions_and_selectable_lists_keep_click_selection() {
|
||||
});
|
||||
};
|
||||
super::test_support::frame(&ctx, vec![], &mut draw);
|
||||
let at = scenario::handle_at("dialog:list:preset").unwrap();
|
||||
let at = solidcraft_ui_registry::handle_at("dialog:list:preset").unwrap();
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, true), &mut draw);
|
||||
super::test_support::frame(&ctx, super::test_support::pointer(at, false), &mut draw);
|
||||
assert!(picked);
|
||||
@@ -2491,8 +2488,11 @@ fn gallery_covers_every_inline_element() {
|
||||
&& matches!(&item.vis, syn::Visibility::Restricted(v) if v.path.is_ident("crate"))
|
||||
{
|
||||
let name = item.sig.ident.to_string();
|
||||
// Inherited labels, state keys and size measurements do not render elements.
|
||||
if !matches!(name.as_str(), "eye_drag_id" | "icon_label_width" | "label" | "placement_field_content_size") {
|
||||
// Inherited labels, state keys and accessors, parsers and size measurements do not render elements.
|
||||
if !matches!(
|
||||
name.as_str(),
|
||||
"eye_drag_id" | "expressions" | "icon_label_width" | "label" | "parse_number" | "placement_field_content_size"
|
||||
) {
|
||||
assert!(names.contains(name.as_str()), "{file}: {name} needs a gallery entry");
|
||||
}
|
||||
}
|
||||
@@ -2681,7 +2681,7 @@ fn command_search_board_in_both_themes() {
|
||||
let mut textures = HashMap::new();
|
||||
let mut image = None;
|
||||
for _ in 0..3 {
|
||||
scenario::clear_handles();
|
||||
solidcraft_ui_registry::clear_handles();
|
||||
let mut output = ctx.run_ui(egui::RawInput { screen_rect: Some(bounds), focused: true, ..Default::default() }, |ui| {
|
||||
ui.painter().rect_filled(bounds, 0.0, Tokens::get().panel);
|
||||
crate::shortcut_box::show(&mut app, ui.ctx());
|
||||
@@ -2690,25 +2690,25 @@ fn command_search_board_in_both_themes() {
|
||||
output.textures_delta.clear();
|
||||
image = Some(raster(&ctx, output.shapes, output.pixels_per_point, bounds, &textures));
|
||||
}
|
||||
let popup = scenario::rect_of("ribbon:search_popup").unwrap();
|
||||
let field = scenario::rect_of("shortcut:search").unwrap();
|
||||
let popup = solidcraft_ui_registry::rect_of("ribbon:search_popup").unwrap();
|
||||
let field = solidcraft_ui_registry::rect_of("shortcut:search").unwrap();
|
||||
assert_eq!(field.height(), 40.0);
|
||||
assert_eq!(field.width(), 520.0);
|
||||
assert_eq!(popup.width(), 520.0);
|
||||
assert_eq!(popup.top(), field.bottom() + Tokens::get().space_6);
|
||||
assert_eq!(popup.x_range(), field.x_range());
|
||||
let footer = scenario::rect_of("ribbon:search_footer").unwrap();
|
||||
let footer = solidcraft_ui_registry::rect_of("ribbon:search_footer").unwrap();
|
||||
assert_eq!(footer, egui::Rect::from_min_max(egui::pos2(popup.left(), footer.top()), popup.right_bottom()));
|
||||
assert!(bounds.contains_rect(popup), "{state}: {popup:?}");
|
||||
match state {
|
||||
"matches" => {
|
||||
assert!(scenario::handle_at("menu:solid.fillet").is_some());
|
||||
assert_eq!(scenario::rect_of("menu:solid.fillet").unwrap().height(), 38.0);
|
||||
assert!(scenario::handle_at("menu:search:design:0").is_some());
|
||||
assert!(solidcraft_ui_registry::handle_at("menu:solid.fillet").is_some());
|
||||
assert_eq!(solidcraft_ui_registry::rect_of("menu:solid.fillet").unwrap().height(), 38.0);
|
||||
assert!(solidcraft_ui_registry::handle_at("menu:search:design:0").is_some());
|
||||
}
|
||||
"command" => assert!(scenario::rect_of("dialog:note:command_parameters").is_some()),
|
||||
"invalid" => assert!(scenario::rect_of("dialog:report:command_parameters").is_some()),
|
||||
"no-match" => assert!(scenario::rect_of("dialog:report:search_no_match").is_some()),
|
||||
"command" => assert!(solidcraft_ui_registry::rect_of("dialog:note:command_parameters").is_some()),
|
||||
"invalid" => assert!(solidcraft_ui_registry::rect_of("dialog:report:command_parameters").is_some()),
|
||||
"no-match" => assert!(solidcraft_ui_registry::rect_of("dialog:report:search_no_match").is_some()),
|
||||
_ => {}
|
||||
}
|
||||
let image = image.unwrap();
|
||||
@@ -2773,7 +2773,7 @@ fn confirm_radial_and_history_rows_keep_multitone_artwork() {
|
||||
ctx.set_visuals(if dark { egui::Visuals::dark() } else { egui::Visuals::light() });
|
||||
let mut app = crate::SolveApp::new(Default::default(), Default::default());
|
||||
app.ui.tab = tab.into();
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let bounds = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(600.0, 360.0));
|
||||
let mut output = ctx.run_ui(egui::RawInput { screen_rect: Some(bounds), ..Default::default() }, |ui| {
|
||||
ui.painter().rect_filled(bounds, 0.0, Tokens::get().panel);
|
||||
@@ -2782,7 +2782,7 @@ fn confirm_radial_and_history_rows_keep_multitone_artwork() {
|
||||
radial_menu(
|
||||
ui,
|
||||
egui::pos2(180.0, 180.0),
|
||||
&[crate::context_menu::Item::action("solid.extrude", "Extrude", icon)],
|
||||
&[super::Item::action("solid.extrude", "Extrude", icon)],
|
||||
(surface == "radial_hover").then_some(0),
|
||||
);
|
||||
}
|
||||
@@ -2913,7 +2913,7 @@ fn command_search_ribbon_transition_clips_content_in_both_themes() {
|
||||
app.ui.palette_open = false;
|
||||
app.sbox.text.clear();
|
||||
}
|
||||
scenario::clear_handles();
|
||||
solidcraft_ui_registry::clear_handles();
|
||||
let mut output = ctx.run_ui(
|
||||
egui::RawInput { screen_rect: Some(bounds), time: Some(f64::from(frame) / 60.0), focused: true, ..Default::default() },
|
||||
|ui| {
|
||||
@@ -2923,12 +2923,12 @@ fn command_search_ribbon_transition_clips_content_in_both_themes() {
|
||||
);
|
||||
textures_update(&mut textures, &output.textures_delta);
|
||||
output.textures_delta.clear();
|
||||
let field = scenario::rect_of("ribbon:search").unwrap();
|
||||
let field = solidcraft_ui_registry::rect_of("ribbon:search").unwrap();
|
||||
if frame == 2 {
|
||||
closed = field;
|
||||
}
|
||||
if (3..14).contains(&frame) {
|
||||
let sheet = scenario::rect_of("ribbon:search_popup").unwrap();
|
||||
let sheet = solidcraft_ui_registry::rect_of("ribbon:search_popup").unwrap();
|
||||
assert!(sheet.top() > field.bottom() && sheet.top() <= field.bottom() + Tokens::get().space_6);
|
||||
// Actual result text is painted with the unfolding sheet's clip,
|
||||
// rather than relying on a smaller background hiding overflow.
|
||||
@@ -3021,7 +3021,7 @@ fn file_view_application_evidence_in_both_themes_and_widths() {
|
||||
textures_update(&mut textures, &output.textures_delta);
|
||||
if frame == 5 {
|
||||
assert!(app.home.open);
|
||||
let page = scenario::rect_of("home:page").expect("actual File view allocation");
|
||||
let page = solidcraft_ui_registry::rect_of("home:page").expect("actual File view allocation");
|
||||
assert!(bounds.contains_rect(page));
|
||||
raster(&ctx, output.shapes.clone(), output.pixels_per_point, bounds, &textures)
|
||||
.save(output_dir.join(format!("file-{name}-{}.png", if dark { "dark" } else { "light" })))
|
||||
|
||||
@@ -239,7 +239,7 @@ impl HistoryTransport {
|
||||
pub fn show(self, ui: &mut egui::Ui, rect: Rect, playing: bool) -> egui::Response {
|
||||
let t = Tokens::get();
|
||||
let response = ui.interact(rect, ui.scope_id().with(("history_transport", self.id())), egui::Sense::click());
|
||||
crate::scenario::publish_target(&format!("history:{}", self.id()), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("history:{}", self.id()), rect);
|
||||
if response.hovered() {
|
||||
ui.painter().rect_filled(rect, t.radius_sm, t.hover);
|
||||
}
|
||||
@@ -399,9 +399,9 @@ pub(crate) fn history_seam(ui: &mut egui::Ui, card: Rect, names: bool) -> Histor
|
||||
ui.label(egui::RichText::new(crate::i18n::tr("ui.history_edge_hint")).size(t.font_small).color(t.text_dim));
|
||||
});
|
||||
}
|
||||
crate::scenario::publish_handle("history:names", seam.pill.center());
|
||||
crate::scenario::publish_rect("history:seam", seam.edge);
|
||||
crate::scenario::publish_rect("history:handle", seam.pill);
|
||||
solidcraft_ui_registry::publish_handle("history:names", seam.pill.center());
|
||||
solidcraft_ui_registry::publish_rect("history:seam", seam.edge);
|
||||
solidcraft_ui_registry::publish_rect("history:handle", seam.pill);
|
||||
// egui's click response includes Enter and Space for a focused button.
|
||||
HistorySeamResponse {
|
||||
toggle: button.clicked() || edge.double_clicked(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Layout elements for dense cards; see `docs/ui-kit.md` for tokens and usage.
|
||||
use super::{metrics::*, text::paint_text};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Response, RichText, Ui, vec2};
|
||||
use solidcraft_engine::kernel::Continuity;
|
||||
|
||||
@@ -98,7 +98,7 @@ pub(crate) fn catalog_footer(ui: &mut Ui, text: &str) -> Response {
|
||||
let galley = ui.painter().layout_job(job);
|
||||
ui.painter().galley(rect.left_top() + vec2(t.space_24, t.space_8), galley, t.form_dim);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), text));
|
||||
crate::scenario::publish_rect("catalog:footer", rect);
|
||||
solidcraft_ui_registry::publish_rect("catalog:footer", rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ impl CatalogLayout {
|
||||
.min_scrolled_height(0.0)
|
||||
.auto_shrink([false, false])
|
||||
.show(ui, add);
|
||||
scenario::publish_rect(&format!("{}:body", self.key), scroll.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:body", self.key), scroll.inner_rect);
|
||||
scroll.inner
|
||||
}
|
||||
|
||||
@@ -157,8 +157,8 @@ impl CatalogLayout {
|
||||
if (height - self.footer_height).abs() > 0.5 {
|
||||
ui.ctx().request_discard("catalog footer measurement changed");
|
||||
}
|
||||
scenario::publish_rect(&format!("{}:content", self.key), self.bounds);
|
||||
scenario::publish_rect(&format!("{}:footer", self.key), scroll.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:content", self.key), self.bounds);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:footer", self.key), scroll.inner_rect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ pub(super) fn publish(ui: &Ui, response: &Response, kind: egui::Role) {
|
||||
egui::Role::TextInput => {
|
||||
t.input_bevel(ui, response);
|
||||
if response.has_focus() {
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
super::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
}
|
||||
egui::Role::ComboBox | egui::Role::Button => t.button_bevel(ui, response, false),
|
||||
@@ -208,7 +208,7 @@ pub(crate) fn status_line(ui: &mut Ui, text: &str, tone: StatusTone) -> Response
|
||||
StatusTone::Error => t.error,
|
||||
};
|
||||
let response = ui.add(egui::Label::new(RichText::new(crate::i18n::tr_label(text)).size(t.font_label).color(ink)).wrap());
|
||||
scenario::publish_rect(&format!("dialog:status:{text}"), response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:status:{text}"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -299,31 +299,31 @@ fn scrub_row<R>(ui: &mut Ui, text: &str, control: impl FnOnce(&mut Ui, Option<&R
|
||||
|
||||
fn row_with<R>(ui: &mut Ui, text: &str, height: f32, scrub: bool, control: impl FnOnce(&mut Ui, Option<&Response>) -> R) -> R {
|
||||
let mut passthrough = false;
|
||||
if crate::prefs_search::querying(ui) {
|
||||
if super::settings_search::querying(ui) {
|
||||
// A row with no label has no words to match; its controls supply them (#9265).
|
||||
let unlabelled = text.is_empty();
|
||||
if unlabelled {
|
||||
crate::prefs_search::set_unlabelled_control(ui);
|
||||
super::settings_search::set_unlabelled_control(ui);
|
||||
} else {
|
||||
crate::prefs_search::set_control(ui, text);
|
||||
super::settings_search::set_control(ui, text);
|
||||
}
|
||||
if !crate::prefs_search::visible(ui, &[text]) {
|
||||
if !super::settings_search::visible(ui, &[text]) {
|
||||
if unlabelled {
|
||||
crate::prefs_search::push_unlabelled(ui);
|
||||
super::settings_search::push_unlabelled(ui);
|
||||
}
|
||||
let result = crate::prefs_search::run_hidden(ui, text, |ui| control(ui, None));
|
||||
let result = super::settings_search::run_hidden(ui, text, |ui| control(ui, None));
|
||||
if unlabelled {
|
||||
crate::prefs_search::pop_unlabelled(ui);
|
||||
super::settings_search::pop_unlabelled(ui);
|
||||
}
|
||||
crate::prefs_search::recheck(ui, &[text]);
|
||||
super::settings_search::recheck(ui, &[text]);
|
||||
return result;
|
||||
}
|
||||
crate::prefs_search::push_passthrough(ui);
|
||||
super::settings_search::push_passthrough(ui);
|
||||
passthrough = true;
|
||||
}
|
||||
let result = sized_row_body(ui, text, height, scrub, control);
|
||||
if passthrough {
|
||||
crate::prefs_search::pop_passthrough(ui);
|
||||
super::settings_search::pop_passthrough(ui);
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -332,8 +332,8 @@ fn sized_row_body<R>(ui: &mut Ui, text: &str, height: f32, scrub: bool, control:
|
||||
let t = Tokens::get();
|
||||
let width = dimension(ui.available_width());
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(width, height), egui::Sense::hover());
|
||||
crate::prefs_search::remember(ui, text, rect);
|
||||
crate::prefs_search::follow(ui, text, rect);
|
||||
super::settings_search::remember(ui, text, rect);
|
||||
super::settings_search::follow(ui, text, rect);
|
||||
// A nested row (`" Of"`) lines up under the one above: the lookup uses the text without its
|
||||
// indent and the indent stays in front of the translation. The `LABEL` tag keeps the original.
|
||||
let shown = indented_label(text);
|
||||
@@ -359,8 +359,8 @@ fn sized_row_body<R>(ui: &mut Ui, text: &str, height: f32, scrub: bool, control:
|
||||
)
|
||||
.inner;
|
||||
let help = super::help_icon(ui, help);
|
||||
scenario::publish_rect(&format!("dialog:row_label:{text}"), label.rect);
|
||||
scenario::publish_rect(&format!("dialog:row_help:{text}"), help.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:row_label:{text}"), label.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:row_help:{text}"), help.rect);
|
||||
} else {
|
||||
ui.add(egui::Label::new(RichText::new(shown.clone()).size(t.font_label).color(t.dialog_label)).truncate())
|
||||
.backing_on_hover_text(shown);
|
||||
@@ -388,10 +388,10 @@ fn sized_row_body<R>(ui: &mut Ui, text: &str, height: f32, scrub: bool, control:
|
||||
},
|
||||
)
|
||||
.inner;
|
||||
scenario::publish_rect(&format!("dialog:row:{text}"), rect);
|
||||
scenario::publish_rect(&format!("dialog:label:{text}"), label_rect);
|
||||
scenario::publish_rect(&format!("dialog:control:{text}"), control_rect);
|
||||
scenario::publish_handle(&format!("dialog:row:{text}"), rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:row:{text}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:label:{text}"), label_rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:control:{text}"), control_rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:row:{text}"), rect.center());
|
||||
result
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ pub(crate) fn warning_caption(ui: &mut Ui, aligned_to: &str, text: &str) {
|
||||
}
|
||||
|
||||
fn caption_in(ui: &mut Ui, aligned_to: &str, text: &str, color: egui::Color32) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -418,7 +418,7 @@ fn caption_in(ui: &mut Ui, aligned_to: &str, text: &str, color: egui::Color32) {
|
||||
let left = (rect.left() + label_column(ui, aligned_to, width) + label_gap(ui, width)).min(rect.right());
|
||||
let text_rect = egui::Rect::from_min_max(egui::pos2(left, rect.top()), rect.max);
|
||||
paint_text(ui, text_rect, text, egui::FontId::proportional(t.font_small), color, false);
|
||||
scenario::publish_rect(&format!("dialog:caption:{aligned_to}"), text_rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:caption:{aligned_to}"), text_rect);
|
||||
}
|
||||
|
||||
/// Natural-height content aligned beneath an indexed row's control, bounded independently of the form.
|
||||
@@ -485,8 +485,8 @@ pub(crate) fn figure_rows(
|
||||
top += slot + t.space_4;
|
||||
}
|
||||
paint(ui.painter(), figure, &slots);
|
||||
scenario::publish_rect(&format!("dialog:figure:{id}"), rect);
|
||||
scenario::publish_rect(&format!("dialog:figure_picture:{id}"), figure);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:figure:{id}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:figure_picture:{id}"), figure);
|
||||
enter
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ pub(crate) fn section_heading(ui: &mut Ui, title: &str) {
|
||||
let t = Tokens::get();
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(dimension(ui.available_width()), t.space_16), egui::Sense::hover());
|
||||
paint_text(ui, rect, &crate::i18n::tr_label(title).to_uppercase(), crate::fonts::semibold(t.font_caption), t.form_dim, true);
|
||||
scenario::publish_rect(&format!("dialog:heading:{title}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:heading:{title}"), rect);
|
||||
}
|
||||
|
||||
pub(crate) fn section<R>(ui: &mut Ui, id: impl egui::AsIdSalt, title: &str, default_open: bool, add: impl FnOnce(&mut Ui) -> R) -> Option<R> {
|
||||
@@ -570,10 +570,10 @@ fn section_with_badge<R>(
|
||||
let (count, pending, compact) = badge;
|
||||
// A search still walks the body, so a setting inside a folded section can match, without
|
||||
// leaving the folded heading on screen for every query.
|
||||
if crate::prefs_search::querying(ui) && crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[title]) {
|
||||
if super::settings_search::querying(ui) && super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[title]) {
|
||||
return Some(add(ui));
|
||||
}
|
||||
let searching = crate::prefs_search::querying(ui);
|
||||
let searching = super::settings_search::querying(ui);
|
||||
let t = Tokens::get();
|
||||
ui.add_space(t.space_2);
|
||||
let id = ui.make_persistent_id(id);
|
||||
@@ -591,7 +591,7 @@ fn section_with_badge<R>(
|
||||
),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
crate::prefs_search::follow(ui, title, rect);
|
||||
super::settings_search::follow(ui, title, rect);
|
||||
if response.hovered() || response.has_focus() {
|
||||
ui.painter().rect_filled(rect.shrink2(vec2(0.0, 1.0)), t.radius_sm, t.hover);
|
||||
}
|
||||
@@ -629,7 +629,7 @@ fn section_with_badge<R>(
|
||||
egui::FontId::proportional(t.font_small),
|
||||
t.form_dim,
|
||||
);
|
||||
scenario::publish_rect(&format!("dialog:section:{title}:count"), badge);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:section:{title}:count"), badge);
|
||||
if pending {
|
||||
rule_right = badge.left() - t.space_12;
|
||||
} else {
|
||||
@@ -642,8 +642,8 @@ fn section_with_badge<R>(
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::DisclosureTriangle, ui.is_enabled(), open, crate::i18n::tr_label(title)));
|
||||
// A disclosure says whether it is open as expanded, as egui's own collapsing header does.
|
||||
ui.ctx().accesskit_node_builder(response.id, |node| node.set_expanded(open));
|
||||
scenario::publish_handle(&format!("dialog:section:{title}"), response.rect.center());
|
||||
scenario::publish_rect(&format!("dialog:section:{title}"), rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:section:{title}"), response.rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:section:{title}"), rect);
|
||||
if response.clicked() && !pending && !searching {
|
||||
open = !open;
|
||||
}
|
||||
@@ -666,13 +666,13 @@ fn section_with_badge<R>(
|
||||
pub(crate) fn search_fold<R>(ui: &mut Ui, title: &str, count: usize, add: impl FnOnce(&mut Ui) -> R) -> Option<R> {
|
||||
let t = Tokens::get();
|
||||
let id = egui::Id::unique(("prefs_search_fold", title));
|
||||
let query = crate::prefs_search::query(ui);
|
||||
let query = super::settings_search::query(ui);
|
||||
let mut open = ui.data(|data| data.get_temp::<bool>(id)).unwrap_or(false);
|
||||
let previous = ui.data(|data| data.get_temp::<String>(id.with("query"))).unwrap_or_default();
|
||||
if previous != query {
|
||||
open = false;
|
||||
}
|
||||
crate::prefs_search::reveal(ui);
|
||||
super::settings_search::reveal(ui);
|
||||
ui.add_space(t.space_2);
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(dimension(ui.available_width()), row_height()), egui::Sense::click());
|
||||
if response.hovered() || response.has_focus() {
|
||||
@@ -717,8 +717,8 @@ pub(crate) fn search_fold<R>(ui: &mut Ui, title: &str, count: usize, add: impl F
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::DisclosureTriangle, ui.is_enabled(), open, &accessible));
|
||||
// A disclosure says whether it is open as expanded, as egui's own collapsing header does.
|
||||
ui.ctx().accesskit_node_builder(response.id, |node| node.set_expanded(open));
|
||||
scenario::publish_rect("prefs:search:shortcuts", rect);
|
||||
scenario::publish_handle("prefs:search:shortcuts", response.rect.center());
|
||||
solidcraft_ui_registry::publish_rect("prefs:search:shortcuts", rect);
|
||||
solidcraft_ui_registry::publish_handle("prefs:search:shortcuts", response.rect.center());
|
||||
if response.clicked() {
|
||||
open = !open;
|
||||
}
|
||||
@@ -727,9 +727,9 @@ pub(crate) fn search_fold<R>(ui: &mut Ui, title: &str, count: usize, add: impl F
|
||||
data.insert_temp(id, open);
|
||||
});
|
||||
open.then(|| {
|
||||
crate::prefs_search::push_force(ui);
|
||||
super::settings_search::push_force(ui);
|
||||
let result = add(ui);
|
||||
crate::prefs_search::pop_force(ui);
|
||||
super::settings_search::pop_force(ui);
|
||||
result
|
||||
})
|
||||
}
|
||||
@@ -745,20 +745,20 @@ pub(crate) fn list_row(ui: &mut Ui, key: &str, text: &str) -> Response {
|
||||
let label = egui::Rect::from_min_max(rect.min, egui::pos2((remove.left() - t.space_8).max(rect.left()), rect.bottom()));
|
||||
paint_text(ui, label, text, egui::FontId::proportional(t.font_label), t.form_ink, false);
|
||||
let response = ui.push_id(key, |ui| super::icon_button_at(ui, remove, "close", "Remove")).inner;
|
||||
scenario::publish_rect(&format!("dialog:list:{key}"), rect);
|
||||
scenario::publish_handle(&format!("dialog:list:{key}:remove"), remove.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:list:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:list:{key}:remove"), remove.center());
|
||||
response.union(row)
|
||||
}
|
||||
|
||||
/// Full-width rule; Tokens: dialog_rule. Use between groups without extra padding.
|
||||
/// Full-width rule without extra padding. Tokens: `dialog_rule`.
|
||||
pub(crate) fn divider(ui: &mut Ui, key: &str) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(dimension(ui.available_width()), 1.0), egui::Sense::hover());
|
||||
Tokens::get().secondary_hline(ui.painter(), rect.x_range(), rect.top(), Tokens::get().dialog_rule);
|
||||
scenario::publish_rect(&format!("dialog:divider:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:divider:{key}"), rect);
|
||||
}
|
||||
|
||||
/// Give a compound row's subfield its accessible name without allocating layout space.
|
||||
@@ -771,12 +771,12 @@ pub(crate) fn named_control<R>(ui: &mut Ui, label: &str, add: impl FnOnce(&mut U
|
||||
/// Publish geometry without replacing native accessibility. Tokens: `none`.
|
||||
pub(super) fn publish_geometry(ui: &Ui, response: &Response) {
|
||||
let label = label(ui);
|
||||
scenario::publish_rect(&format!("dialog:widget:{label}"), response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:widget:{label}"), response.rect);
|
||||
// A click lands where the field shows: a field scrolled out of a dialog's field area, under
|
||||
// its footer, has no handle (a click at its centre would press the button over it).
|
||||
let shown = response.rect.intersect(ui.clip_rect());
|
||||
if shown.is_positive() {
|
||||
scenario::publish_handle(&format!("dialog:{label}"), shown.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:{label}"), shown.center());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,7 +797,7 @@ pub(crate) fn text_lines(ui: &mut Ui, id: &str, text: &str) {
|
||||
ui.add(egui::Label::new(RichText::new(*line).font(font.clone()).color(t.form_ink)).truncate()).backing_on_hover_text(*line);
|
||||
}
|
||||
});
|
||||
scenario::publish_rect(&format!("dialog:lines:{id}"), response.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:lines:{id}"), response.inner_rect);
|
||||
}
|
||||
|
||||
/// Read-only text variants retain their original wrapping, allocation and scenario identities.
|
||||
@@ -837,20 +837,20 @@ pub(crate) fn name_cell_with_hint(ui: &mut Ui, name: &str, strong: bool, hint: &
|
||||
}
|
||||
|
||||
fn text_impl(ui: &mut Ui, text: &str, style: Text<'_>, hover_hint: Option<&str>) -> Response {
|
||||
if crate::prefs_search::querying(ui) {
|
||||
if super::settings_search::querying(ui) {
|
||||
let heading = matches!(style, Text::Heading(_));
|
||||
if heading {
|
||||
let hit = crate::prefs_search::texts_hit(ui, &[text]);
|
||||
crate::prefs_search::set_heading_open(ui, hit);
|
||||
let hit = super::settings_search::texts_hit(ui, &[text]);
|
||||
super::settings_search::set_heading_open(ui, hit);
|
||||
if hit {
|
||||
crate::prefs_search::reveal(ui);
|
||||
} else if crate::prefs_search::suppress_chrome(ui) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::reveal(ui);
|
||||
} else if super::settings_search::suppress_chrome(ui) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
} else if crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[text]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
} else if crate::prefs_search::texts_hit(ui, &[text]) && !crate::prefs_search::shows_all(ui) {
|
||||
crate::prefs_search::reveal(ui);
|
||||
} else if super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[text]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
} else if super::settings_search::texts_hit(ui, &[text]) && !super::settings_search::shows_all(ui) {
|
||||
super::settings_search::reveal(ui);
|
||||
}
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -922,13 +922,13 @@ fn text_impl(ui: &mut Ui, text: &str, style: Text<'_>, hover_hint: Option<&str>)
|
||||
Text::Cell | Text::MonoCell | Text::NameCell(_) | Text::SelectedNameCell => ("cell", text),
|
||||
};
|
||||
let id = format!("dialog:{kind}:{key}");
|
||||
scenario::publish_rect(&id, response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&id, response.rect);
|
||||
if matches!(style, Text::Heading(_)) {
|
||||
crate::prefs_search::remember(ui, text, response.rect);
|
||||
crate::prefs_search::follow(ui, text, response.rect);
|
||||
super::settings_search::remember(ui, text, response.rect);
|
||||
super::settings_search::follow(ui, text, response.rect);
|
||||
}
|
||||
if !matches!(style, Text::Note(_) | Text::MonoNote(_)) {
|
||||
scenario::publish_handle(&id, response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle(&id, response.rect.center());
|
||||
}
|
||||
response
|
||||
}
|
||||
@@ -956,8 +956,8 @@ pub(crate) fn summary_row(ui: &mut Ui, name: &str, summary: &str, enabled: &mut
|
||||
paint_text(ui, detail, summary, egui::FontId::proportional(t.font_small), t.form_dim, false);
|
||||
let response = ui.interact(text, ui.scope_id().with("summary"), egui::Sense::hover()).backing_on_hover_text(summary);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), format!("{name}: {summary}")));
|
||||
scenario::publish_rect(&format!("dialog:inline:{name}"), title);
|
||||
scenario::publish_rect("dialog:report:rule_report", detail);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:inline:{name}"), title);
|
||||
solidcraft_ui_registry::publish_rect("dialog:report:rule_report", detail);
|
||||
ui.scope_builder(
|
||||
egui::UiBuilder::new()
|
||||
.max_rect(egui::Rect::from_min_max(egui::pos2(right, rect.top()), rect.max))
|
||||
|
||||
@@ -262,7 +262,7 @@ fn mouse_mapping_search_keeps_main_gesture_chords_and_actions() {
|
||||
let output = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| {
|
||||
scope(ui, |ui| {
|
||||
ui.set_width(280.0);
|
||||
let _search = crate::prefs_search::begin(ui, query);
|
||||
let _search = crate::ui_kit::settings_search::begin(ui, query);
|
||||
settings_mouse_mapping(
|
||||
ui,
|
||||
&[(vec!["Ctrl/Cmd".into(), "Middle drag".into()], "Zoom toward the cursor".into()), (vec!["Alt/Option".into()], "Orbit".into())],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Appearance inheritance selector. The caller owns selection and commands.
|
||||
use super::{Sphere, sphere::paint_sphere};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Rect, Sense, Stroke, Ui, pos2, vec2};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -79,10 +79,10 @@ pub fn look_chain(ui: &mut Ui, id: &str, labels: [&str; 4], looks: [Option<Spher
|
||||
let label_left = (cell.center().x - label_width / 2.0).max(cell.left());
|
||||
let label_rect = Rect::from_min_max(pos2(label_left, cell.top() + side + t.space_2), cell.max);
|
||||
super::text::paint_text(ui, label_rect, label, font, if step == active { t.render_accent } else { t.form_dim }, false);
|
||||
scenario::publish_rect(&key, cell);
|
||||
scenario::publish_handle(&key, cell.center());
|
||||
solidcraft_ui_registry::publish_rect(&key, cell);
|
||||
solidcraft_ui_registry::publish_handle(&key, cell.center());
|
||||
}
|
||||
scenario::publish_rect(id, rect);
|
||||
solidcraft_ui_registry::publish_rect(id, rect);
|
||||
clicked
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::scenario;
|
||||
use crate::ui_kit::{
|
||||
FinishKind,
|
||||
test_support::{frame, pointer},
|
||||
|
||||
@@ -1,7 +1,104 @@
|
||||
//! Menus share one popup surface and one row painter. Command dispatch stays with callers.
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::{context_menu::Item, fonts::RichTextExt as _, icons, theme::Tokens};
|
||||
use crate::{fonts::RichTextExt as _, icons, theme::Tokens};
|
||||
use egui::{Color32, FontId, Rect, Response, Sense, Stroke, Ui, pos2, vec2};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
/// One menu entry: a command id (run with `params` when given, else started like a toolbar
|
||||
/// click) or a `ui.*` action. `-` is a separator, `#…` a section heading.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Item {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub shortcut: String,
|
||||
pub enabled: bool,
|
||||
pub icon: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub children: Vec<Item>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub swatch: Option<[u8; 3]>,
|
||||
/// Why the entry is off: shown on hover, and in the control channel's report.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tip: Option<String>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
/// A view action (`ui.*`).
|
||||
pub fn action(id: &str, label: &str, icon: &str) -> Item {
|
||||
Item {
|
||||
id: id.into(),
|
||||
label: label.into(),
|
||||
shortcut: String::new(),
|
||||
enabled: true,
|
||||
icon: icon.into(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
pub fn sep() -> Item {
|
||||
Item {
|
||||
id: "-".into(),
|
||||
label: String::new(),
|
||||
shortcut: String::new(),
|
||||
enabled: false,
|
||||
icon: String::new(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
pub(crate) fn heading(label: &str) -> Item {
|
||||
Item {
|
||||
id: format!("#{label}"),
|
||||
label: label.into(),
|
||||
shortcut: String::new(),
|
||||
enabled: false,
|
||||
icon: String::new(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
pub(crate) fn is_sep(&self) -> bool {
|
||||
self.id == "-"
|
||||
}
|
||||
pub(crate) fn is_heading(&self) -> bool {
|
||||
self.id.starts_with('#')
|
||||
}
|
||||
pub fn with(mut self, params: Value) -> Item {
|
||||
self.params = Some(params);
|
||||
self
|
||||
}
|
||||
pub fn on_if(self, enabled: bool) -> Item {
|
||||
self.on(enabled)
|
||||
}
|
||||
pub(crate) fn on(mut self, enabled: bool) -> Item {
|
||||
self.enabled &= enabled;
|
||||
self
|
||||
}
|
||||
/// Off, saying why, when there is a reason.
|
||||
pub(crate) fn off_because(mut self, why: Option<String>) -> Item {
|
||||
if let Some(why) = why {
|
||||
self.enabled = false;
|
||||
self.tip = Some(why);
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn key(mut self, k: &str) -> Item {
|
||||
self.shortcut = k.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Width of list menus.
|
||||
pub const LIST_W: f32 = 250.0;
|
||||
|
||||
pub(crate) struct Menu;
|
||||
|
||||
@@ -244,7 +341,7 @@ impl Menu {
|
||||
leftward: bool,
|
||||
add: impl FnOnce(&mut Ui) -> R,
|
||||
) -> egui::InnerResponse<R> {
|
||||
let width = crate::context_menu::LIST_W;
|
||||
let width = LIST_W;
|
||||
let screen = ctx.content_rect();
|
||||
// The estimate only places the area's first, invisible sizing pass.
|
||||
let height = ctx.memory(|m| m.area_rect(id)).map_or(height, |r| r.height());
|
||||
@@ -362,7 +459,7 @@ impl<'a> MenuItem<'a> {
|
||||
let galley = painter.layout_job(job);
|
||||
painter.galley(pos2(left, r.center().y - galley.size().y / 2.0), galley, ink);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, enabled, label));
|
||||
crate::scenario::publish_target(&format!("menu:{}", self.id), r);
|
||||
solidcraft_ui_registry::publish_target(&format!("menu:{}", self.id), r);
|
||||
if enabled && response.hovered() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||
}
|
||||
@@ -408,7 +505,7 @@ pub(crate) fn workspace_menu(ui: &mut egui::Ui, items: &[Item], key_row: Option<
|
||||
if item.enabled { Sense::click() } else { Sense::hover() },
|
||||
);
|
||||
for handle in [format!("menu:{}", item.id), format!("workspace:menu:{slug}")] {
|
||||
crate::scenario::publish_target(&handle, r);
|
||||
solidcraft_ui_registry::publish_target(&handle, r);
|
||||
}
|
||||
if current || (item.enabled && (resp.hovered() || key_row == Some(k))) {
|
||||
ui.painter().rect_filled(r, t.control_radius.saturating_sub(1), soft);
|
||||
@@ -497,7 +594,7 @@ pub(crate) fn rename_menu(ui: &mut Ui, value: &mut String, focused: &mut bool) -
|
||||
let id = egui::Id::unique("sc_rename_text");
|
||||
let response = ui.add(super::fields::text_input(egui::TextEdit::singleline(value).id(id)).desired_width(ui.available_width()));
|
||||
t.input_bevel(ui, &response);
|
||||
crate::scenario::publish_target("rename:name", response.rect);
|
||||
solidcraft_ui_registry::publish_target("rename:name", response.rect);
|
||||
if !*focused {
|
||||
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), id) {
|
||||
state.cursor.set_char_range(Some(egui::text::CCursorRange::two(
|
||||
|
||||
@@ -42,9 +42,11 @@ mod preferences_layout;
|
||||
mod preferences_navigation;
|
||||
mod preferences_small;
|
||||
mod radial;
|
||||
pub(crate) mod recent_colors;
|
||||
mod reports;
|
||||
mod review;
|
||||
mod search;
|
||||
pub(crate) mod settings_search;
|
||||
mod shortcuts;
|
||||
mod sphere;
|
||||
mod stages;
|
||||
@@ -56,6 +58,7 @@ mod theme_picker;
|
||||
mod tree;
|
||||
mod value_focus;
|
||||
mod viewport_status;
|
||||
mod wheel;
|
||||
|
||||
pub(crate) use actions::*;
|
||||
pub use actions::{Button, button};
|
||||
@@ -76,17 +79,19 @@ pub(crate) use details::*;
|
||||
pub(crate) use disclosure::*;
|
||||
pub(crate) use drag::*;
|
||||
pub(crate) use fields::*;
|
||||
pub use fields::{cell_editor, wide_cell_editor};
|
||||
pub use fields::{Expressions, cell_editor, install_expressions, wide_cell_editor};
|
||||
pub(crate) use file_view::{FileView, RecentRow, RecoveryMessage, SampleCard};
|
||||
pub(crate) use framing::*;
|
||||
pub(crate) use history::*;
|
||||
pub(crate) use history_group::*;
|
||||
pub(crate) use layout::*;
|
||||
pub use look_chain::{LookStep, look_chain};
|
||||
pub use menu::{Item, LIST_W};
|
||||
pub(crate) use menu::{Menu, MenuItem, rename_menu, workspace_menu};
|
||||
pub(crate) use metrics::{ContentWidth, header_height, row_height, table_row_height};
|
||||
pub use name_list::{NameList, NameRow, name_list};
|
||||
pub(crate) use navigation::*;
|
||||
pub use navigation::{NavigationEdge, NavigationIdle, NavigationSize};
|
||||
pub(crate) use navigation_menu::*;
|
||||
pub(crate) use navigation_settings::*;
|
||||
pub(crate) use panel::{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! cells leave. Built on the kit's table grid; see `docs/ui-kit.md` for tokens and usage.
|
||||
use super::metrics::dimension;
|
||||
use super::table::{Column, Table, TableRow, table_custom};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Response, Ui, vec2};
|
||||
|
||||
/// Columns past this are ignored: a list of named rows has a handful of cells.
|
||||
@@ -111,7 +111,7 @@ fn name_text(ui: &mut Ui, name: &str) -> Response {
|
||||
super::text::paint_text(ui, rect, name, font, t.form_ink, false);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), name));
|
||||
let id = format!("dialog:name:{name}");
|
||||
scenario::publish_rect(&id, rect);
|
||||
scenario::publish_count(&format!("{id}:cut"), f64::from(u8::from(cut)));
|
||||
solidcraft_ui_registry::publish_rect(&id, rect);
|
||||
solidcraft_ui_registry::publish_count(&format!("{id}:cut"), f64::from(u8::from(cut)));
|
||||
if cut { response.backing_on_hover_text(name) } else { response }
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
//! Navigation chrome and measurements shared by the viewport and Preferences editor.
|
||||
use crate::{
|
||||
icons,
|
||||
navigation_bar::{Idle, Kind, Size},
|
||||
theme::Tokens,
|
||||
};
|
||||
use crate::{icons, theme::Tokens};
|
||||
use egui::{Rect, Response, Sense, Ui, pos2, vec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(crate) const NAV_BORDER: f32 = 1.0;
|
||||
pub(crate) const NAV_PADDING: f32 = 3.0;
|
||||
@@ -41,6 +38,48 @@ pub(crate) const NAV_FADE_SECONDS: f32 = 0.12;
|
||||
pub(crate) const NAV_TOOLTIP_DELAY: f64 = 0.8;
|
||||
pub(crate) const NAV_TOOLTIP_KEY_LINE_HEIGHT: f32 = 15.0;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum NavigationKind {
|
||||
Action,
|
||||
Tool,
|
||||
Toggle,
|
||||
Menu,
|
||||
Separator,
|
||||
}
|
||||
|
||||
/// The edge of the 3D view the bar sits on (#8183): the bottom, or the top, under the ribbon.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NavigationEdge {
|
||||
#[default]
|
||||
Bottom,
|
||||
Top,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NavigationSize {
|
||||
Compact,
|
||||
#[default]
|
||||
Regular,
|
||||
}
|
||||
|
||||
impl NavigationSize {
|
||||
// Logical points: egui's UI zoom applies the preference's scale to these dimensions.
|
||||
pub(crate) fn points(self) -> f32 {
|
||||
NavigationMetrics::for_size(self).button
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NavigationIdle {
|
||||
Always,
|
||||
#[default]
|
||||
Fade,
|
||||
Hide,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct NavigationMetrics {
|
||||
pub button: f32,
|
||||
@@ -49,10 +88,10 @@ pub(crate) struct NavigationMetrics {
|
||||
pub button_radius: u8,
|
||||
}
|
||||
impl NavigationMetrics {
|
||||
pub(crate) fn for_size(size: Size) -> Self {
|
||||
pub(crate) fn for_size(size: NavigationSize) -> Self {
|
||||
match size {
|
||||
Size::Regular => Self { button: 32.0, icon: 18.0, radius: 10, button_radius: 7 },
|
||||
Size::Compact => Self { button: 26.0, icon: 16.0, radius: 9, button_radius: 6 },
|
||||
NavigationSize::Regular => Self { button: 32.0, icon: 18.0, radius: 10, button_radius: 7 },
|
||||
NavigationSize::Compact => Self { button: 26.0, icon: 16.0, radius: 9, button_radius: 6 },
|
||||
}
|
||||
}
|
||||
pub(crate) fn height(self) -> f32 {
|
||||
@@ -65,7 +104,7 @@ pub(crate) struct NavigationButton<'a> {
|
||||
pub label: &'a str,
|
||||
pub icon: &'a str,
|
||||
pub selected: bool,
|
||||
pub kind: Kind,
|
||||
pub kind: NavigationKind,
|
||||
pub labels: bool,
|
||||
pub menu: bool,
|
||||
}
|
||||
@@ -79,10 +118,10 @@ pub(crate) enum NavigationFill {
|
||||
}
|
||||
|
||||
/// Actions never latch, and keyboard focus has its own outline rather than a fill.
|
||||
pub(crate) fn navigation_visual(kind: Kind, active: bool, hovered: bool, pressed: bool, keyboard_focus: bool) -> (NavigationFill, bool) {
|
||||
let fill = if pressed || (active && kind == Kind::Menu) {
|
||||
pub(crate) fn navigation_visual(kind: NavigationKind, active: bool, hovered: bool, pressed: bool, keyboard_focus: bool) -> (NavigationFill, bool) {
|
||||
let fill = if pressed || (active && kind == NavigationKind::Menu) {
|
||||
NavigationFill::Press
|
||||
} else if active && matches!(kind, Kind::Tool | Kind::Toggle) {
|
||||
} else if active && matches!(kind, NavigationKind::Tool | NavigationKind::Toggle) {
|
||||
NavigationFill::Active
|
||||
} else if hovered {
|
||||
NavigationFill::Hover
|
||||
@@ -114,12 +153,12 @@ pub(crate) fn navigation_ink(t: &Tokens, fill: NavigationFill) -> egui::Color32
|
||||
/// WCAG small-text contrast for an active slot's label on its selected fill.
|
||||
pub(crate) const NAV_ACTIVE_MIN_CONTRAST: f64 = 4.5;
|
||||
|
||||
pub(crate) fn navigation_opacity(idle: Idle, active: bool) -> f32 {
|
||||
pub(crate) fn navigation_opacity(idle: NavigationIdle, active: bool) -> f32 {
|
||||
match idle {
|
||||
_ if active => 1.0,
|
||||
Idle::Always => 1.0,
|
||||
Idle::Fade => NAV_IDLE_OPACITY,
|
||||
Idle::Hide => 0.0,
|
||||
NavigationIdle::Always => 1.0,
|
||||
NavigationIdle::Fade => NAV_IDLE_OPACITY,
|
||||
NavigationIdle::Hide => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +168,11 @@ pub(crate) fn navigation_button(ui: &mut Ui, rect: Rect, button: NavigationButto
|
||||
|
||||
pub(crate) fn navigation_button_editable(ui: &mut Ui, rect: Rect, button: NavigationButton<'_>, editing: bool) -> Response {
|
||||
let t = Tokens::get();
|
||||
let metrics =
|
||||
NavigationMetrics::for_size(if rect.height() < NavigationMetrics::for_size(Size::Regular).button { Size::Compact } else { Size::Regular });
|
||||
let metrics = NavigationMetrics::for_size(if rect.height() < NavigationMetrics::for_size(NavigationSize::Regular).button {
|
||||
NavigationSize::Compact
|
||||
} else {
|
||||
NavigationSize::Regular
|
||||
});
|
||||
let response = ui.interact(rect, ui.make_persistent_id(("nav_slot", button.id)), if editing { Sense::click_and_drag() } else { Sense::click() });
|
||||
let pressed = response.is_pointer_button_down_on();
|
||||
if pressed || response.clicked_by(egui::PointerButton::Primary) {
|
||||
@@ -191,8 +233,11 @@ pub(crate) fn navigation_separator(ui: &Ui, rect: Rect) {
|
||||
|
||||
pub(crate) fn navigation_edit_outline(ui: &Ui, rect: Rect, hidden: bool, selected: bool) {
|
||||
let t = Tokens::get();
|
||||
let metrics =
|
||||
NavigationMetrics::for_size(if rect.height() < NavigationMetrics::for_size(Size::Regular).button { Size::Compact } else { Size::Regular });
|
||||
let metrics = NavigationMetrics::for_size(if rect.height() < NavigationMetrics::for_size(NavigationSize::Regular).button {
|
||||
NavigationSize::Compact
|
||||
} else {
|
||||
NavigationSize::Regular
|
||||
});
|
||||
let mut painter = ui.painter().clone();
|
||||
if hidden {
|
||||
painter.multiply_opacity(NAV_HIDDEN_OPACITY);
|
||||
@@ -219,7 +264,7 @@ pub(crate) fn navigation_edit_outline(ui: &Ui, rect: Rect, hidden: bool, selecte
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn navigation_surface(ui: &Ui, rect: Rect, size: Size, editing: bool) {
|
||||
pub(crate) fn navigation_surface(ui: &Ui, rect: Rect, size: NavigationSize, editing: bool) {
|
||||
let t = Tokens::get();
|
||||
let frame = super::Container::new(ui.ctx(), super::Surface::Navigation)
|
||||
.frame(ui.ctx(), t.dialog_body, crate::frame::Side::Left)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Popup and tooltip skin from the approved navigation boards.
|
||||
use super::{Container, Surface, navigation::*};
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{icons, navigation_bar::Edge, theme::Tokens};
|
||||
use crate::{icons, theme::Tokens};
|
||||
use egui::{Align2, FontId, Rect, Response, Sense, Ui, pos2, vec2};
|
||||
|
||||
pub(crate) fn navigation_popup_frame() -> egui::Frame {
|
||||
@@ -110,7 +110,7 @@ pub(crate) fn navigation_menu_separator(ui: &mut Ui) {
|
||||
|
||||
/// A tip beyond `along` (the bar, or the row it names): above it, or below it for a bar on the
|
||||
/// view's top edge.
|
||||
pub(crate) fn navigation_tooltip(response: &Response, along: Rect, edge: Edge, label: &str, shortcut: Option<&str>) {
|
||||
pub(crate) fn navigation_tooltip(response: &Response, along: Rect, edge: NavigationEdge, label: &str, shortcut: Option<&str>) {
|
||||
let ctx = &response.ctx;
|
||||
let id = response.id.with("nav_tooltip_delay");
|
||||
if !response.hovered() {
|
||||
@@ -130,10 +130,10 @@ pub(crate) fn navigation_tooltip(response: &Response, along: Rect, edge: Edge, l
|
||||
.area(response.id.with("nav_tooltip"))
|
||||
.order(egui::Order::Tooltip)
|
||||
.interactable(false)
|
||||
.pivot(if edge == Edge::Top { Align2::CENTER_TOP } else { Align2::CENTER_BOTTOM })
|
||||
.pivot(if edge == NavigationEdge::Top { Align2::CENTER_TOP } else { Align2::CENTER_BOTTOM })
|
||||
.fixed_pos(match edge {
|
||||
Edge::Bottom => pos2(response.rect.center().x, along.top() - NAV_POPUP_GAP),
|
||||
Edge::Top => pos2(response.rect.center().x, along.bottom() + NAV_POPUP_GAP),
|
||||
NavigationEdge::Bottom => pos2(response.rect.center().x, along.top() - NAV_POPUP_GAP),
|
||||
NavigationEdge::Top => pos2(response.rect.center().x, along.bottom() + NAV_POPUP_GAP),
|
||||
})
|
||||
.show(ctx, |ui| {
|
||||
Container::new(ctx, Surface::Popup).show_frame(ui, frame, ui.max_rect(), |ui| {
|
||||
|
||||
@@ -128,7 +128,7 @@ pub(crate) fn navigation_anchor_picker(ui: &mut Ui, id: &str, selected: usize, l
|
||||
);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::RadioButton, ui.is_enabled(), on, *label));
|
||||
crate::scenario::publish_target(&format!("prefs:navbar:anchor:{index}"), cell);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:navbar:anchor:{index}"), cell);
|
||||
if response.backing_on_hover_text(*label).clicked() {
|
||||
clicked = Some(index);
|
||||
}
|
||||
@@ -241,7 +241,7 @@ pub(crate) fn navigation_tile(ui: &mut Ui, id: &str, label: &str, icon: &str, dr
|
||||
(response, add)
|
||||
}
|
||||
|
||||
pub(crate) fn navigation_drag_ghost(ui: &Ui, at: egui::Pos2, button: NavigationButton<'_>, size: crate::navigation_bar::Size) {
|
||||
pub(crate) fn navigation_drag_ghost(ui: &Ui, at: egui::Pos2, button: NavigationButton<'_>, size: NavigationSize) {
|
||||
let ctx = ui.ctx();
|
||||
let t = Tokens::get();
|
||||
let metrics = NavigationMetrics::for_size(size);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Panel elements for dense cards; see `docs/ui-kit.md` for tokens and usage.
|
||||
use super::{actions::icon_button_at, metrics::*, text::paint_text};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Ui, vec2};
|
||||
|
||||
const WINDOW_PAGES_NARROW_WIDTH: f32 = 150.0;
|
||||
@@ -64,7 +64,7 @@ fn header_actions(
|
||||
);
|
||||
if closeable {
|
||||
let close = icon_button_at(ui, close_rect, "close", "Close");
|
||||
scenario::publish_handle("dialog:close", close.rect.center());
|
||||
solidcraft_ui_registry::publish_handle("dialog:close", close.rect.center());
|
||||
if close.clicked() {
|
||||
on_close();
|
||||
}
|
||||
@@ -83,12 +83,12 @@ fn header_actions(
|
||||
action_ui.set_clip_rect(ui.clip_rect().intersect(action_rect));
|
||||
}
|
||||
actions(&mut action_ui);
|
||||
scenario::publish_rect("dialog:header:accessory", action_rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:header:accessory", action_rect);
|
||||
action_rect.left() - t.space_8
|
||||
} else if let Some(collapsed) = collapsed {
|
||||
let fold = egui::Rect::from_min_size(egui::pos2(close_rect.left() - t.form_close_size - t.space_4, close_rect.top()), close_rect.size());
|
||||
let response = super::disclosure_button_at(ui, fold, !*collapsed, crate::i18n::tr_label(if *collapsed { "Expand" } else { "Collapse" }));
|
||||
scenario::publish_handle("dialog:collapse", response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle("dialog:collapse", response.rect.center());
|
||||
if response.clicked() {
|
||||
*collapsed = !*collapsed;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ fn header_actions(
|
||||
paint_text(ui, title_rect, title, crate::fonts::semibold(if icon.0.is_empty() { t.font_label } else { t.font_heading }), t.form_ink, false);
|
||||
let title_response = ui.interact(title_rect, ui.make_persistent_id("title_text"), egui::Sense::hover());
|
||||
title_response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), title));
|
||||
scenario::publish_rect("dialog:header", rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:header", rect);
|
||||
t.secondary_hline(ui.painter(), rect.x_range(), rect.bottom(), t.dialog_rule);
|
||||
}
|
||||
|
||||
@@ -166,11 +166,11 @@ impl<'a> TopHeader<'a> {
|
||||
// left out: one control for one action, and its width goes to the title. Its handle
|
||||
// moves to Cancel, so automation that closes a panel keeps working.
|
||||
if let Some(cancel_rect) = actions.rect(super::Action::Cancel) {
|
||||
scenario::publish_handle("dialog:close", cancel_rect.center());
|
||||
solidcraft_ui_registry::publish_handle("dialog:close", cancel_rect.center());
|
||||
} else {
|
||||
let close_rect = target(right);
|
||||
let response = icon_button_at(ui, close_rect, "close", "Close");
|
||||
scenario::publish_handle("dialog:close", response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle("dialog:close", response.rect.center());
|
||||
if response.clicked() {
|
||||
*close = true;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ impl<'a> TopHeader<'a> {
|
||||
if let Some(collapsed) = collapsed {
|
||||
let fold = target(right);
|
||||
let response = super::disclosure_button_at(ui, fold, !*collapsed, crate::i18n::tr_label(if *collapsed { "Expand" } else { "Collapse" }));
|
||||
scenario::publish_handle("dialog:collapse", response.rect.center());
|
||||
solidcraft_ui_registry::publish_handle("dialog:collapse", response.rect.center());
|
||||
if response.clicked() {
|
||||
*collapsed = !*collapsed;
|
||||
}
|
||||
@@ -192,8 +192,8 @@ impl<'a> TopHeader<'a> {
|
||||
let left = (icon.right() + t.space_8).min(right);
|
||||
let title = egui::Rect::from_min_max(egui::pos2(left, band.top()), egui::pos2(right.max(left), band.bottom()));
|
||||
// Dragging the panel by its title must not start on a button.
|
||||
scenario::publish_handle("dialog:title", title.center());
|
||||
scenario::publish_rect("dialog:header", band);
|
||||
solidcraft_ui_registry::publish_handle("dialog:title", title.center());
|
||||
solidcraft_ui_registry::publish_rect("dialog:header", band);
|
||||
t.secondary_hline(ui.painter(), band.x_range(), band.bottom(), t.dialog_rule);
|
||||
Self { icon, title, actions }
|
||||
}
|
||||
@@ -243,7 +243,7 @@ pub(crate) fn inspector_header(ui: &mut Ui, icon: &str, title: &str, kind: &str)
|
||||
vec2(t.form_close_size, t.form_close_size),
|
||||
);
|
||||
let close = super::icon_button_at(ui, close_rect, "close", crate::i18n::tr_label("Close"));
|
||||
scenario::publish_target("render:appearance:close", close.rect);
|
||||
solidcraft_ui_registry::publish_target("render:appearance:close", close.rect);
|
||||
if close.clicked() {
|
||||
action = Some(InspectorAction::Close);
|
||||
}
|
||||
@@ -272,7 +272,7 @@ pub(crate) fn inspector_header(ui: &mut Ui, icon: &str, title: &str, kind: &str)
|
||||
ui.interact(name, ui.make_persistent_id("inspector_title"), egui::Sense::hover())
|
||||
.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), title));
|
||||
t.secondary_hline(ui.painter(), rect.x_range(), rect.bottom(), t.dialog_rule);
|
||||
scenario::publish_rect("dialog:header", rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:header", rect);
|
||||
action
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ impl<'a> Panel<'a> {
|
||||
let band = ui.max_rect().expand2(vec2(t.space_12, t.space_8));
|
||||
ui.painter().rect_filled(band, egui::CornerRadius { nw: 0, ne: 0, sw: t.radius_xl, se: t.radius_xl }, t.dialog_footer);
|
||||
t.secondary_hline(ui.painter(), band.x_range(), band.top(), t.dialog_rule);
|
||||
scenario::publish_rect("dialog:editor_footer", band);
|
||||
solidcraft_ui_registry::publish_rect("dialog:editor_footer", band);
|
||||
add(ui)
|
||||
})
|
||||
}
|
||||
@@ -523,18 +523,18 @@ impl<'a> Panel<'a> {
|
||||
let grip = ui
|
||||
.interact(grip_rect, ui.scope_id().with("workspace_grip"), egui::Sense::click_and_drag())
|
||||
.on_hover_cursor(egui::CursorIcon::Grab);
|
||||
scenario::publish_target(&format!("{}:grip", options.id), grip.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:grip", options.id), grip.rect);
|
||||
super::Menu::context(&grip, |ui| {
|
||||
let (target, label) = if left { ("right", "Move to right side") } else { ("left", "Move to left side") };
|
||||
let item = super::MenuItem::new("view.render_dock", label, "").show(ui);
|
||||
scenario::publish_target(&format!("{}:move_side", options.id), item.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:move_side", options.id), item.rect);
|
||||
if item.clicked() {
|
||||
move_side = Some(target);
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
header(ui, &options);
|
||||
scenario::publish_rect(&format!("{}:header", options.id), band);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:header", options.id), band);
|
||||
if grip.dragged() {
|
||||
let frame_nr = ui.ctx().cumulative_frame_nr();
|
||||
ui.ctx().data_mut(|d| d.insert_temp(egui::Id::unique("sc_dock_drag_frame"), frame_nr));
|
||||
@@ -575,7 +575,7 @@ impl<'a> Panel<'a> {
|
||||
);
|
||||
let resize =
|
||||
ui.interact(rail, ui.scope_id().with("workspace_resize"), egui::Sense::drag()).on_hover_cursor(egui::CursorIcon::ResizeHorizontal);
|
||||
scenario::publish_target(&format!("{}:resize", options.id), rail);
|
||||
solidcraft_ui_registry::publish_target(&format!("{}:resize", options.id), rail);
|
||||
if resize.dragged() && !options.compact {
|
||||
let delta = ui.input(|i| i.pointer.delta().x) * if left { 1.0 } else { -1.0 };
|
||||
if delta.is_finite() {
|
||||
@@ -792,7 +792,7 @@ impl<'a> Panel<'a> {
|
||||
let min = ui.cursor().min;
|
||||
let band = egui::Rect::from_min_size(min, vec2(ui.available_width(), super::header_height()));
|
||||
if heading.drag_key.is_empty() {
|
||||
scenario::publish_handle("dialog:title", band.center());
|
||||
solidcraft_ui_registry::publish_handle("dialog:title", band.center());
|
||||
} else {
|
||||
crate::frame::drag_dock(ui, band, heading.drag_key, heading.moved, self.bounded_anchor(ui.ctx()) - heading.docked);
|
||||
}
|
||||
@@ -816,7 +816,7 @@ impl<'a> Panel<'a> {
|
||||
let fields_height = self.body_rows.map_or(fields_height, |rows| fields_height.min(rows * t.control_height + t.space_12 * 2.0));
|
||||
let fields_rect = egui::Rect::from_min_size(ui.cursor().min, vec2(ui.available_width(), fields_height));
|
||||
let state = if collapsed.as_deref().copied().unwrap_or(false) {
|
||||
scenario::publish_count("dialog:body:content_height", 0.0);
|
||||
solidcraft_ui_registry::publish_count("dialog:body:content_height", 0.0);
|
||||
ComputeStatus { text: None, busy: disabled, applying: disabled }
|
||||
} else {
|
||||
ui.scope_builder(egui::UiBuilder::new().max_rect(fields_rect), |ui| {
|
||||
@@ -839,9 +839,9 @@ impl<'a> Panel<'a> {
|
||||
.show(ui, body)
|
||||
.inner
|
||||
});
|
||||
scenario::publish_count("dialog:body:content_height", f64::from(scroll.content_size.y));
|
||||
scenario::publish_count("dialog:body:scroll_y", f64::from(scroll.state.clamped_offset().y));
|
||||
scenario::publish_rect("dialog:body:viewport", scroll.inner_rect);
|
||||
solidcraft_ui_registry::publish_count("dialog:body:content_height", f64::from(scroll.content_size.y));
|
||||
solidcraft_ui_registry::publish_count("dialog:body:scroll_y", f64::from(scroll.state.clamped_offset().y));
|
||||
solidcraft_ui_registry::publish_rect("dialog:body:viewport", scroll.inner_rect);
|
||||
scroll.inner
|
||||
})
|
||||
.inner
|
||||
@@ -864,7 +864,7 @@ impl<'a> Panel<'a> {
|
||||
if state.busy {
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(t.space_16, ui.available_height()), egui::Sense::hover());
|
||||
egui::Spinner::new().size(t.space_14).paint_at(ui, rect);
|
||||
scenario::publish_rect("dialog:footer:spinner", rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:footer:spinner", rect);
|
||||
let status = state.text.as_deref().unwrap_or_else(|| crate::i18n::tr_label("Updating the preview"));
|
||||
ui.interact(rect, ui.scope_id().with("compute_status"), egui::Sense::hover()).backing_on_hover_text(status);
|
||||
ui.ctx().request_repaint_after(std::time::Duration::from_millis(100));
|
||||
@@ -915,7 +915,7 @@ impl<'a> Panel<'a> {
|
||||
.show(ui, body)
|
||||
})
|
||||
.inner;
|
||||
scenario::publish_rect("sketch_palette:options", scroll.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect("sketch_palette:options", scroll.inner_rect);
|
||||
}
|
||||
if !at_top {
|
||||
add(ui, Part::Footer);
|
||||
@@ -928,7 +928,7 @@ impl<'a> Panel<'a> {
|
||||
.min_scrolled_height(0.0)
|
||||
.auto_shrink([false, true])
|
||||
.show(ui, contents);
|
||||
scenario::publish_rect("sketch_palette:options", scroll.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect("sketch_palette:options", scroll.inner_rect);
|
||||
} else {
|
||||
contents(ui);
|
||||
}
|
||||
@@ -1254,7 +1254,7 @@ impl<'a> Window<'a> {
|
||||
super::Container::finish_content(ui, frame);
|
||||
result
|
||||
});
|
||||
scenario::publish_rect("dialog:window", shown.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:window", shown.response.rect);
|
||||
return shown.inner;
|
||||
}
|
||||
let window = super::Container::new(ctx, super::Surface::Window)
|
||||
@@ -1281,7 +1281,7 @@ impl<'a> Window<'a> {
|
||||
result
|
||||
});
|
||||
if let Some(shown) = &shown {
|
||||
scenario::publish_rect("dialog:window", shown.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:window", shown.response.rect);
|
||||
}
|
||||
shown.and_then(|r| r.inner).unwrap_or((false, super::Action::None))
|
||||
}
|
||||
@@ -1330,7 +1330,7 @@ fn page_list_sized<S>(
|
||||
let nav = egui::Rect::from_min_size(body.min, vec2(width, body.height()));
|
||||
ui.painter().rect_filled(nav, 0.0, t.dialog_footer);
|
||||
t.secondary_vline(ui.painter(), nav.right(), nav.y_range(), t.dialog_rule);
|
||||
scenario::publish_rect("dialog:pages", nav);
|
||||
solidcraft_ui_registry::publish_rect("dialog:pages", nav);
|
||||
*selected = (*selected).min(pages.len().saturating_sub(1));
|
||||
let nav_inner = if sidebar_footer.is_some() {
|
||||
egui::Rect::from_min_max(nav.min + vec2(t.space_8, t.space_6), nav.max - vec2(t.space_8, t.space_10))
|
||||
@@ -1389,7 +1389,7 @@ fn page_list_sized<S>(
|
||||
if response.hovered() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||
}
|
||||
scenario::publish_target(page.handle, rect);
|
||||
solidcraft_ui_registry::publish_target(page.handle, rect);
|
||||
response.backing_on_hover_text(label);
|
||||
}
|
||||
});
|
||||
@@ -1400,7 +1400,7 @@ fn page_list_sized<S>(
|
||||
ui.set_clip_rect(ui.clip_rect().intersect(rect));
|
||||
footer(ui, state);
|
||||
});
|
||||
scenario::publish_rect("dialog:pages_footer", rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:pages_footer", rect);
|
||||
}
|
||||
let content = egui::Rect::from_min_max(egui::pos2(nav.right(), body.top()), body.max);
|
||||
let padding = egui::epaint::MarginF32 {
|
||||
@@ -1421,14 +1421,14 @@ fn page_list_sized<S>(
|
||||
if let Some(header) = header {
|
||||
header(ui, state);
|
||||
}
|
||||
let force_scroll = search_scroll && crate::prefs_search::force_scroll(ui.ctx());
|
||||
let force_scroll = search_scroll && super::settings_search::force_scroll(ui.ctx());
|
||||
if let Some(page) = pages.get(*selected) {
|
||||
if page.scroll || force_scroll {
|
||||
let scroll = super::settings_scroll(ui, ("sc_prefs_page", *selected), |ui| (page.draw)(ui, state));
|
||||
scenario::publish_rect("dialog:page_body", scroll.inner_rect);
|
||||
solidcraft_ui_registry::publish_rect("dialog:page_body", scroll.inner_rect);
|
||||
} else {
|
||||
(page.draw)(ui, state);
|
||||
scenario::publish_rect("dialog:page_body", content);
|
||||
solidcraft_ui_registry::publish_rect("dialog:page_body", content);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
//! never additional application viewports; every colour and control metric comes from tokens.
|
||||
pub(crate) use super::preferences_figure_data::{paint_assembly_move, paint_direct_interaction, paint_measure_axis_distances, paint_tool_cursor};
|
||||
use super::{metrics::dimension, text::paint_text};
|
||||
use crate::theme::{ColorVision, Tokens};
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{
|
||||
scenario,
|
||||
theme::{ColorVision, Tokens},
|
||||
};
|
||||
use egui::{Color32, FontId, Pos2, Rect, Response, Sense, Stroke, Ui, pos2, vec2};
|
||||
|
||||
pub(super) const PAGE_SCROLL_GUTTER: &str = "sc_page_scroll_gutter";
|
||||
@@ -35,7 +32,7 @@ fn blur_cost_meter(ui: &mut Ui, radius: f32) {
|
||||
ui.painter().rect_filled(bar, t.radius_sm, if i < filled { t.form_primary } else { t.dialog_rule });
|
||||
}
|
||||
}
|
||||
scenario::publish_rect("prefs:blur_cost", rect);
|
||||
solidcraft_ui_registry::publish_rect("prefs:blur_cost", rect);
|
||||
response.backing_on_hover_text(crate::i18n::tr("prefs.blur_cost"));
|
||||
}
|
||||
|
||||
@@ -52,11 +49,11 @@ pub(crate) fn theme_option_notice(ui: &mut Ui, id: &str, text: &str, can_undo: b
|
||||
let mut draw_actions = |ui: &mut Ui| {
|
||||
if can_undo {
|
||||
let response = super::text_button(ui, crate::i18n::tr("ui.undo"), true);
|
||||
scenario::publish_target("prefs:theme_notice_undo:guard", response.rect);
|
||||
solidcraft_ui_registry::publish_target("prefs:theme_notice_undo:guard", response.rect);
|
||||
undo = response.clicked();
|
||||
}
|
||||
let response = super::text_button(ui, dismiss_label, true);
|
||||
scenario::publish_target(&format!("prefs:theme_notice_dismiss:{id}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:theme_notice_dismiss:{id}"), response.rect);
|
||||
dismiss = response.clicked();
|
||||
};
|
||||
let width = dimension(ui.available_width());
|
||||
@@ -80,7 +77,7 @@ pub(crate) fn theme_option_notice(ui: &mut Ui, id: &str, text: &str, can_undo: b
|
||||
});
|
||||
}
|
||||
});
|
||||
scenario::publish_rect(&format!("prefs:theme_notice:{id}"), response.response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:theme_notice:{id}"), response.response.rect);
|
||||
(undo, dismiss)
|
||||
}
|
||||
|
||||
@@ -218,11 +215,11 @@ pub(crate) fn theme_options_list(
|
||||
);
|
||||
if options.iter().any(|option| values.get(&option.definition.id).is_some_and(|v| *v != option.definition.default_value())) {
|
||||
let response = super::text_button(ui, crate::i18n::tr("prefs.theme_options_reset"), true);
|
||||
scenario::publish_target("prefs:theme_options_reset", response.rect);
|
||||
solidcraft_ui_registry::publish_target("prefs:theme_options_reset", response.rect);
|
||||
reset = response.clicked();
|
||||
}
|
||||
});
|
||||
scenario::publish_rect("prefs:theme_options_header", header.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("prefs:theme_options_header", header.response.rect);
|
||||
let mut draw_rows = |ui: &mut Ui, rows: &[Vec<usize>]| {
|
||||
let mut section = None;
|
||||
for row in rows {
|
||||
@@ -266,7 +263,10 @@ pub(crate) fn theme_options_list(
|
||||
}
|
||||
});
|
||||
if let Some(option) = cell.first().and_then(|i| options.get(*i)) {
|
||||
scenario::publish_rect(&format!("prefs:theme_option_cell:{}", option.definition.id), response.response.rect);
|
||||
solidcraft_ui_registry::publish_rect(
|
||||
&format!("prefs:theme_option_cell:{}", option.definition.id),
|
||||
response.response.rect,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -278,8 +278,8 @@ pub(crate) fn theme_options_list(
|
||||
if plan.more_count > 0 {
|
||||
let title = crate::i18n::format("prefs.theme_options_more", &[("count", &plan.more_count.to_string())]);
|
||||
super::section(ui, "more", &title, false, |ui| draw_rows(ui, &plan.more));
|
||||
if let Some(rect) = scenario::rect_of(&format!("dialog:section:{title}")) {
|
||||
scenario::publish_target("prefs:theme_options_more", rect);
|
||||
if let Some(rect) = solidcraft_ui_registry::rect_of(&format!("dialog:section:{title}")) {
|
||||
solidcraft_ui_registry::publish_target("prefs:theme_options_more", rect);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -351,7 +351,7 @@ fn theme_option_control(
|
||||
super::text(ui, &label, super::Text::Inline);
|
||||
},
|
||||
);
|
||||
scenario::publish_rect(&format!("prefs:theme_option_readout:{}", def.id), readout.response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:theme_option_readout:{}", def.id), readout.response.rect);
|
||||
if option.blur {
|
||||
blur_cost_meter(ui, *n);
|
||||
}
|
||||
@@ -378,10 +378,10 @@ fn theme_option_control(
|
||||
.response
|
||||
});
|
||||
let response = response.inner.backing_on_hover_text(&option.help);
|
||||
scenario::publish_target(&format!("prefs:theme_option:{}", def.id), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:theme_option:{}", def.id), response.rect);
|
||||
if let Some(reason) = &option.reason {
|
||||
let note = ui.horizontal(|ui| super::settings_inline_note(ui, reason));
|
||||
scenario::publish_rect(&format!("prefs:theme_option_reason:{}", def.id), note.response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:theme_option_reason:{}", def.id), note.response.rect);
|
||||
}
|
||||
if active && value != before {
|
||||
values.insert(def.id.clone(), value);
|
||||
@@ -402,7 +402,7 @@ fn theme_option_switch(ui: &mut Ui, label: &str, on: &mut bool) {
|
||||
ui.painter().rect_stroke(rect, t.control_radius, Stroke::new(t.space_2, t.form_primary), egui::StrokeKind::Inside);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::CheckBox, ui.is_enabled(), *on, label));
|
||||
scenario::publish_target(&format!("prefs:theme_option_switch:{label}"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:theme_option_switch:{label}"), response.rect);
|
||||
}
|
||||
|
||||
/// Page scroller keeps content aligned while centring its bar in the window's right inset.
|
||||
@@ -451,7 +451,7 @@ pub(crate) fn settings_heading(ui: &mut Ui, title: &str, description: &str) {
|
||||
}
|
||||
|
||||
pub(crate) fn settings_heading_with_tag(ui: &mut Ui, title: &str, description: &str, tag: Option<fn(&mut Ui)>) {
|
||||
if crate::prefs_search::querying(ui) {
|
||||
if super::settings_search::querying(ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -478,7 +478,7 @@ pub(crate) fn settings_heading_with_tag(ui: &mut Ui, title: &str, description: &
|
||||
super::text(ui, description, super::Text::Hint("settings_heading"));
|
||||
});
|
||||
if page {
|
||||
scenario::publish_rect("prefs:page_heading", heading.response.rect);
|
||||
solidcraft_ui_registry::publish_rect("prefs:page_heading", heading.response.rect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,33 +505,33 @@ fn settings_section_count_spaced<R>(
|
||||
compact: bool,
|
||||
mut add: impl FnMut(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
if crate::prefs_search::querying(ui) {
|
||||
crate::prefs_search::begin_section(ui, title);
|
||||
if super::settings_search::querying(ui) {
|
||||
super::settings_search::begin_section(ui, title);
|
||||
if !ui.is_visible() {
|
||||
return Some(add(ui));
|
||||
}
|
||||
if crate::prefs_search::shows_all(ui) {
|
||||
let mut child = crate::prefs_search::detached_child(ui, ("prefs-search-section", key), false);
|
||||
if super::settings_search::shows_all(ui) {
|
||||
let mut child = super::settings_search::detached_child(ui, ("prefs-search-section", key), false);
|
||||
let result = add(&mut child);
|
||||
crate::prefs_search::place_search_child(ui, &child);
|
||||
super::settings_search::place_search_child(ui, &child);
|
||||
return Some(result);
|
||||
}
|
||||
let mark = crate::prefs_search::search_layout_mark(ui);
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let mark = super::settings_search::search_layout_mark(ui);
|
||||
let before = super::settings_search::hits(ui);
|
||||
let (gained, missed) = {
|
||||
let mut probe = crate::prefs_search::detached_child(ui, ("prefs-search-section-probe", key), true);
|
||||
let mut probe = super::settings_search::detached_child(ui, ("prefs-search-section-probe", key), true);
|
||||
let missed = add(&mut probe);
|
||||
(crate::prefs_search::hits(ui) > before, missed)
|
||||
(super::settings_search::hits(ui) > before, missed)
|
||||
};
|
||||
crate::prefs_search::restore_search_layout(ui, mark);
|
||||
super::settings_search::restore_search_layout(ui, mark);
|
||||
if !gained {
|
||||
return Some(missed);
|
||||
}
|
||||
// Keep search headings outside wrapped rows in this section.
|
||||
crate::prefs_search::reveal(ui);
|
||||
let mut child = crate::prefs_search::detached_child(ui, ("prefs-search-section", key), false);
|
||||
super::settings_search::reveal(ui);
|
||||
let mut child = super::settings_search::detached_child(ui, ("prefs-search-section", key), false);
|
||||
let result = add(&mut child);
|
||||
crate::prefs_search::place_search_child(ui, &child);
|
||||
super::settings_search::place_search_child(ui, &child);
|
||||
return Some(result);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -547,14 +547,14 @@ fn settings_section_count_spaced<R>(
|
||||
ui.spacing_mut().item_spacing.y = t.space_10;
|
||||
}
|
||||
let result = super::section_count(ui, key, title, true, count, |ui| {
|
||||
if let Some(rect) = scenario::rect_of(&format!("dialog:section:{title}")) {
|
||||
scenario::publish_rect(&format!("prefs:section:{key}"), rect);
|
||||
if let Some(rect) = solidcraft_ui_registry::rect_of(&format!("dialog:section:{title}")) {
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:section:{key}"), rect);
|
||||
}
|
||||
ui.spacing_mut().item_spacing.y = if page { t.space_14 } else { t.space_8 };
|
||||
let start = ui.next_widget_position();
|
||||
let result = add(ui);
|
||||
if page {
|
||||
scenario::publish_rect(&format!("prefs:section:{key}:body"), Rect::from_min_max(start, ui.min_rect().max));
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:section:{key}:body"), Rect::from_min_max(start, ui.min_rect().max));
|
||||
}
|
||||
result
|
||||
});
|
||||
@@ -580,7 +580,7 @@ pub(super) fn settings_card_frame(ui: &Ui) -> egui::Frame {
|
||||
|
||||
/// Controls and one explanatory specimen side by side, stacked on narrow viewports.
|
||||
pub(crate) fn settings_pair<R>(ui: &mut Ui, controls: impl FnOnce(&mut Ui) -> R, preview: impl FnOnce(&mut Ui, R)) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
let _ = controls(ui);
|
||||
return;
|
||||
}
|
||||
@@ -607,7 +607,7 @@ pub(crate) fn settings_pair<R>(ui: &mut Ui, controls: impl FnOnce(&mut Ui) -> R,
|
||||
|
||||
/// Compact informational state badge for settings; never an interactive control.
|
||||
pub(crate) fn settings_badge(ui: &mut Ui, text: &str, warning: bool) {
|
||||
if crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[text]) {
|
||||
if super::settings_search::suppress_chrome(ui) && !super::settings_search::texts_hit(ui, &[text]) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -642,13 +642,13 @@ pub(crate) enum PreferenceFigure {
|
||||
/// Reusable illustrated radio card. Width comes from the figure, and wrapping is owned by
|
||||
/// the parent; labels remain accessible even when translations need an ellipsis.
|
||||
pub(crate) fn option_card(ui: &mut Ui, label: &str, note: Option<&str>, selected: bool, enabled: bool, figure: PreferenceFigure) -> Response {
|
||||
if crate::prefs_search::querying(ui) {
|
||||
if super::settings_search::querying(ui) {
|
||||
let show = match note {
|
||||
Some(note) => crate::prefs_search::visible(ui, &[label, note]),
|
||||
None => crate::prefs_search::visible(ui, &[label]),
|
||||
Some(note) => super::settings_search::visible(ui, &[label, note]),
|
||||
None => super::settings_search::visible(ui, &[label]),
|
||||
};
|
||||
if !show {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -725,7 +725,7 @@ pub(crate) fn option_card(ui: &mut Ui, label: &str, note: Option<&str>, selected
|
||||
ui.painter().line_segment([centre + vec2(-t.space_2, t.space_2), centre + vec2(t.space_4, -t.space_3)], Stroke::new(2.0, ink));
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::RadioButton, enabled, selected, label));
|
||||
scenario::publish_target(&format!("prefs:option:{label}"), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:option:{label}"), rect);
|
||||
let response = response.backing_on_hover_text(label);
|
||||
if enabled { response.on_hover_cursor(egui::CursorIcon::PointingHand) } else { response }
|
||||
}
|
||||
@@ -925,7 +925,7 @@ pub(crate) fn settings_font_row(ui: &mut Ui, label: &str, mut add: impl FnMut(&m
|
||||
|
||||
/// Natural-width dim text, for inline suffixes and metadata without a full-width note row.
|
||||
pub(crate) fn settings_inline_note(ui: &mut Ui, text: &str) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -941,7 +941,7 @@ pub(crate) fn settings_inline_note(ui: &mut Ui, text: &str) {
|
||||
|
||||
/// Framed font specimen, inheriting the chosen application font and its fallback glyphs.
|
||||
pub(crate) fn font_specimen(ui: &mut Ui, text: &str) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -952,7 +952,10 @@ pub(crate) fn font_specimen(ui: &mut Ui, text: &str) {
|
||||
|
||||
/// Intrinsically sized segments, retaining the usual row label for accessibility and automation.
|
||||
pub(crate) fn settings_segments(ui: &mut Ui, label: &str, selected: &mut usize, options: &[&str]) -> bool {
|
||||
if crate::prefs_search::suppress_chrome(ui) && !crate::prefs_search::texts_hit(ui, &[label]) && !crate::prefs_search::texts_hit(ui, options) {
|
||||
if super::settings_search::suppress_chrome(ui)
|
||||
&& !super::settings_search::texts_hit(ui, &[label])
|
||||
&& !super::settings_search::texts_hit(ui, options)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -969,7 +972,7 @@ pub(crate) fn settings_segments(ui: &mut Ui, label: &str, selected: &mut usize,
|
||||
}
|
||||
|
||||
pub(crate) fn status_palette_sample(ui: &mut Ui, mode: ColorVision) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -985,7 +988,7 @@ pub(crate) fn status_palette_sample(ui: &mut Ui, mode: ColorVision) {
|
||||
let label = crate::i18n::tr(label);
|
||||
let galley = ui.painter().layout_no_wrap(label.to_owned(), crate::fonts::semibold(t.font_small), ink);
|
||||
let (r, _) = ui.allocate_exact_size(vec2(galley.size().x + t.space_24 + t.space_16, t.space_24), Sense::hover());
|
||||
scenario::publish_rect(&format!("prefs:status_sample:{label}"), r);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:status_sample:{label}"), r);
|
||||
ui.painter().rect_filled(r, t.radius_xl, fill);
|
||||
crate::icons::paint_ink(
|
||||
ui.painter(),
|
||||
@@ -1007,7 +1010,7 @@ pub(crate) const COLOUR_VISION_SAMPLE: [&str; 6] =
|
||||
/// colours of `t` (the preference passes the mode being chosen, #7152). The six cells share one
|
||||
/// responsive, clipped area.
|
||||
pub(crate) fn colour_vision_sample(ui: &mut egui::Ui, t: &Tokens) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let count = COLOUR_VISION_SAMPLE.len() as f32;
|
||||
@@ -1015,11 +1018,11 @@ pub(crate) fn colour_vision_sample(ui: &mut egui::Ui, t: &Tokens) {
|
||||
let width = ((dimension(ui.available_width()) - gap * (count - 1.0)) / count).min(t.space_24 * 3.0 + t.space_12);
|
||||
let figure_height = t.space_24 + t.space_16 + t.space_4;
|
||||
let (area, _) = ui.allocate_exact_size(vec2(width * count + gap * (count - 1.0), figure_height + t.space_3 + t.space_16), Sense::hover());
|
||||
scenario::publish_rect("prefs:colour_vision_samples", area);
|
||||
solidcraft_ui_registry::publish_rect("prefs:colour_vision_samples", area);
|
||||
let p = ui.painter_at(area);
|
||||
for (i, name) in COLOUR_VISION_SAMPLE.iter().enumerate() {
|
||||
let cell = egui::Rect::from_min_size(area.min + vec2((width + gap) * i as f32, 0.0), vec2(width, figure_height));
|
||||
scenario::publish_rect(&format!("prefs:colour_vision_sample:{i}"), cell);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:colour_vision_sample:{i}"), cell);
|
||||
super::preferences_figure_ops::paint_colour_vision(&p, cell, i, t);
|
||||
centred_text(
|
||||
&p,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Shared concise choice cards and the agent-pointer settings specimen.
|
||||
use super::{metrics::dimension, text::paint_text};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{FontId, Rect, Response, Sense, Stroke, Ui, pos2, vec2};
|
||||
|
||||
/// One concise choice with its full meaning available to keyboard users and on hover.
|
||||
@@ -15,17 +15,17 @@ pub(crate) struct SettingsChoice<'a> {
|
||||
|
||||
/// Equal option cards keep the choices comparable without repeating long explanations.
|
||||
pub(crate) fn settings_choices(ui: &mut Ui, choices: &[SettingsChoice<'_>], selected: &mut usize) -> bool {
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::shows_all(ui) {
|
||||
if super::settings_search::querying(ui) && !super::settings_search::shows_all(ui) {
|
||||
let keep: Vec<usize> = choices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, choice)| crate::prefs_search::texts_hit(ui, &[choice.title, choice.summary]))
|
||||
.filter(|(_, choice)| super::settings_search::texts_hit(ui, &[choice.title, choice.summary]))
|
||||
.map(|(index, _)| index)
|
||||
.collect();
|
||||
if keep.is_empty() {
|
||||
return false;
|
||||
}
|
||||
crate::prefs_search::reveal(ui);
|
||||
super::settings_search::reveal(ui);
|
||||
return choice_row(ui, choices, selected, &keep);
|
||||
}
|
||||
let keep: Vec<usize> = (0..choices.len()).collect();
|
||||
@@ -119,7 +119,7 @@ fn choice_card(ui: &mut Ui, width: f32, choice: &SettingsChoice<'_>, selected: b
|
||||
if let Some(icon) = choice.icon {
|
||||
let cue = Rect::from_min_size(at, vec2(t.space_14, t.space_14));
|
||||
crate::icons::paint_icon(ui.painter(), cue, icon, 1.0);
|
||||
scenario::publish_rect(&format!("{}:icon", choice.key), cue);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:icon", choice.key), cue);
|
||||
at.x += icon_width;
|
||||
}
|
||||
let summary_at = pos2(rect.left() + t.space_12, at.y + title_galley.size().y + t.space_6);
|
||||
@@ -127,16 +127,16 @@ fn choice_card(ui: &mut Ui, width: f32, choice: &SettingsChoice<'_>, selected: b
|
||||
ui.painter().galley(summary_at, summary_galley, t.form_dim);
|
||||
}
|
||||
let texts = [choice.title, choice.summary];
|
||||
if let Some(text) = texts.iter().find(|text| crate::prefs_search::texts_hit(ui, &[*text])) {
|
||||
crate::prefs_search::remember(ui, text, rect);
|
||||
if let Some(text) = texts.iter().find(|text| super::settings_search::texts_hit(ui, &[*text])) {
|
||||
super::settings_search::remember(ui, text, rect);
|
||||
}
|
||||
for text in texts {
|
||||
crate::prefs_search::follow(ui, text, rect);
|
||||
super::settings_search::follow(ui, text, rect);
|
||||
}
|
||||
response.widget_info(|| {
|
||||
egui::WidgetInfo::selected(egui::Role::RadioButton, ui.is_enabled(), selected, format!("{}: {}", choice.title, choice.detail))
|
||||
});
|
||||
scenario::publish_target(choice.key, rect);
|
||||
solidcraft_ui_registry::publish_target(choice.key, rect);
|
||||
response.backing_on_hover_text(choice.detail).on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//! every colour comes from `Tokens`. A card sizes to the width its container gives it and its
|
||||
//! figure keeps the figure's aspect ratio.
|
||||
use super::{metrics::dimension, preferences::centred_text};
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Color32, FontId, Painter, Rect, Response, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
|
||||
|
||||
/// How a card or tile looks in a state. Kept separate from painting so the state rules can be tested.
|
||||
@@ -111,9 +111,9 @@ pub(crate) fn settings_row_card<R>(ui: &mut Ui, selected: bool, height: f32, add
|
||||
}
|
||||
|
||||
fn paint_card(ui: &mut Ui, card: &Card, width: f32, tile: bool) -> Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[card.label]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[card.label, card.sub.unwrap_or("")]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[card.label]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[card.label, card.sub.unwrap_or("")]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let width = dimension(width);
|
||||
@@ -122,8 +122,8 @@ fn paint_card(ui: &mut Ui, card: &Card, width: f32, tile: bool) -> Response {
|
||||
// Labelled compound rows own their search index (settings_picture_choices, for example).
|
||||
// Standalone cards and tiles are independently searchable settings.
|
||||
if super::layout::label(ui).is_empty() {
|
||||
crate::prefs_search::remember(ui, card.label, rect);
|
||||
crate::prefs_search::follow(ui, card.label, rect);
|
||||
super::settings_search::remember(ui, card.label, rect);
|
||||
super::settings_search::follow(ui, card.label, rect);
|
||||
}
|
||||
let look = card_look(card.selected, enabled, response.hovered(), response.has_focus());
|
||||
let p = ui.painter();
|
||||
@@ -187,7 +187,7 @@ fn paint_card(ui: &mut Ui, card: &Card, width: f32, tile: bool) -> Response {
|
||||
}
|
||||
let kind = if tile { egui::Role::CheckBox } else { egui::Role::RadioButton };
|
||||
response.widget_info(|| egui::WidgetInfo::selected(kind, enabled, card.selected, card.label));
|
||||
scenario::publish_target(&format!("prefs:card:{}", card.label), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:card:{}", card.label), rect);
|
||||
let response = response.backing_on_hover_text(card.label);
|
||||
let response = if let Some(options) = card.options { response.backing_on_hover_text(options) } else { response };
|
||||
if enabled { response.on_hover_cursor(egui::CursorIcon::PointingHand) } else { response }
|
||||
@@ -204,7 +204,7 @@ fn tick(p: &Painter, r: Rect, ink: Color32) {
|
||||
|
||||
/// A radio card: a figure, a label and an optional sub-label. Click it to choose it.
|
||||
pub(crate) fn picture_card(ui: &mut Ui, card: &Card, width: f32) -> Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[card.sub.unwrap_or("")]);
|
||||
super::settings_search::note_unlabelled(ui, &[card.sub.unwrap_or("")]);
|
||||
paint_card(ui, card, width, false)
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ pub(crate) fn settings_picture_choices_sized(
|
||||
+ if size.caption { t.space_16 + t.space_10 } else { 0.0 };
|
||||
super::settings_form(ui, 0.0, |ui| {
|
||||
super::layout::sized_row(ui, label, height, |ui| {
|
||||
crate::prefs_search::note_choices(ui, &options.iter().map(|o| o.label).collect::<Vec<_>>());
|
||||
super::settings_search::note_choices(ui, &options.iter().map(|o| o.label).collect::<Vec<_>>());
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(t.space_10, t.space_10);
|
||||
// Use the real control child, including the scrollbar and form bounds. Round
|
||||
@@ -283,19 +283,19 @@ pub(crate) fn settings_picture_choices_sized(
|
||||
super::settings_inline_note(ui, label);
|
||||
}
|
||||
let mut picked = None;
|
||||
let mut indexed = crate::prefs_search::texts_hit(ui, &[label]);
|
||||
let mut indexed = super::settings_search::texts_hit(ui, &[label]);
|
||||
for (row, choices) in options.chunks(columns).enumerate() {
|
||||
ui.horizontal_top(|ui| {
|
||||
for (column, option) in choices.iter().enumerate() {
|
||||
let index = row * columns + column;
|
||||
let response = picture_card(ui, &option.card(selected == index, enabled, size.aspect), width);
|
||||
scenario::publish_target(&format!("prefs:{key}:{}", option.key), response.rect);
|
||||
scenario::publish_target(&format!("dialog:{label}:{}", option.label), response.rect);
|
||||
if !indexed && crate::prefs_search::texts_hit(ui, &[option.label]) {
|
||||
crate::prefs_search::remember(ui, option.label, response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:{key}:{}", option.key), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("dialog:{label}:{}", option.label), response.rect);
|
||||
if !indexed && super::settings_search::texts_hit(ui, &[option.label]) {
|
||||
super::settings_search::remember(ui, option.label, response.rect);
|
||||
indexed = true;
|
||||
}
|
||||
crate::prefs_search::follow(ui, option.label, response.rect);
|
||||
super::settings_search::follow(ui, option.label, response.rect);
|
||||
if response.clicked() && enabled {
|
||||
picked = Some(index);
|
||||
}
|
||||
@@ -311,7 +311,7 @@ pub(crate) fn settings_picture_choices_sized(
|
||||
|
||||
/// A toggle tile: the same picture with a checkbox in the corner. Click it to flip `card.selected`.
|
||||
pub(crate) fn toggle_tile(ui: &mut Ui, card: &Card, width: f32) -> Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[card.sub.unwrap_or("")]);
|
||||
super::settings_search::note_unlabelled(ui, &[card.sub.unwrap_or("")]);
|
||||
paint_card(ui, card, width, true)
|
||||
}
|
||||
|
||||
@@ -326,9 +326,9 @@ pub(crate) fn preset_card(
|
||||
icon: &dyn Fn(&Painter, Rect, Color32),
|
||||
width: f32,
|
||||
) -> Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[name, word]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[name, word]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[name, word]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[name, word]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let enabled = enabled && ui.is_enabled();
|
||||
@@ -336,8 +336,8 @@ pub(crate) fn preset_card(
|
||||
let height = t.space_24 * 3.0 - t.space_2;
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(width, height), if enabled { Sense::click() } else { Sense::hover() });
|
||||
if super::layout::label(ui).is_empty() {
|
||||
crate::prefs_search::remember(ui, name, rect);
|
||||
crate::prefs_search::follow(ui, name, rect);
|
||||
super::settings_search::remember(ui, name, rect);
|
||||
super::settings_search::follow(ui, name, rect);
|
||||
}
|
||||
let look = card_look(selected, enabled, response.hovered(), response.has_focus());
|
||||
let dim = |c: Color32| c.gamma_multiply(look.alpha);
|
||||
@@ -373,7 +373,7 @@ pub(crate) fn preset_card(
|
||||
tick(p, Rect::from_center_size(centre, vec2(t.space_12, t.space_12)), dim(t.on_accent));
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::RadioButton, enabled, selected, name));
|
||||
scenario::publish_target(&format!("prefs:preset:{name}"), rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:preset:{name}"), rect);
|
||||
if enabled { response.on_hover_cursor(egui::CursorIcon::PointingHand) } else { response }
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ pub(crate) fn help_icon(ui: &mut Ui, text: &str) -> Response {
|
||||
p.circle_filled(pos2(rect.center().x, rect.top() + size * 0.3), 0.9, ink);
|
||||
p.line_segment([pos2(rect.center().x, rect.top() + size * 0.46), pos2(rect.center().x, rect.bottom() - size * 0.24)], Stroke::new(1.2, ink));
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), text));
|
||||
scenario::publish_target("prefs:help_icon", rect);
|
||||
solidcraft_ui_registry::publish_target("prefs:help_icon", rect);
|
||||
response.backing_on_hover_text(text)
|
||||
}
|
||||
|
||||
@@ -439,10 +439,10 @@ pub(crate) fn settings_plugin_card(
|
||||
let tile = Rect::from_center_size(pos2(rect.left() + tile_side / 2.0, header_center), vec2(tile_side, tile_side));
|
||||
ui.painter().rect_filled(tile, t.radius_lg, t.selected_bg);
|
||||
crate::icons::paint_ink(ui.painter(), tile.shrink(t.space_8), "settings_plug", t.form_primary);
|
||||
scenario::publish_rect(&format!("prefs:plugin:{key}:icon"), tile);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:plugin:{key}:icon"), tile);
|
||||
let at = pos2(tile.right() + t.space_12, header_center - text_height / 2.0);
|
||||
let title_rect = Rect::from_min_size(at, title.size());
|
||||
scenario::publish_rect(&format!("prefs:plugin:{key}:title"), title_rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:plugin:{key}:title"), title_rect);
|
||||
let version_at = pos2(at.x + title.size().x + t.space_8, at.y + (header_height - version_galley.size().y) / 2.0);
|
||||
ui.painter().galley(pos2(at.x, at.y + (header_height - title.size().y) / 2.0), title, t.form_ink);
|
||||
ui.painter().galley(version_at, version_galley, t.form_dim);
|
||||
|
||||
@@ -230,7 +230,7 @@ pub(crate) fn paint_layout(ui: &mut egui::Ui, state: super::preferences::LayoutP
|
||||
panel(inset, inset, 120.0 - inset * 2.0, ribbon_height, "", t.toolbar);
|
||||
let browser =
|
||||
panel(bx, top, bw, browser_height, "Browser", if state.translucent && !state.compact { t.panel.gamma_multiply(0.60) } else { t.panel });
|
||||
crate::scenario::publish_rect("prefs:layout_preview:browser", browser);
|
||||
solidcraft_ui_registry::publish_rect("prefs:layout_preview:browser", browser);
|
||||
// A full row of quiet tool symbols, with label marks only when labels are enabled.
|
||||
let quiet = t.form_dim.lerp_to_gamma(t.viewport_top, 0.55);
|
||||
for index in 0..10 {
|
||||
@@ -261,7 +261,7 @@ pub(crate) fn paint_layout(ui: &mut egui::Ui, state: super::preferences::LayoutP
|
||||
_ => Some(panel(inset, bottom - 10.0, 120.0 - inset * 2.0, 10.0, "History", t.selected_bg)),
|
||||
};
|
||||
if let Some(history) = history {
|
||||
crate::scenario::publish_rect("prefs:layout_preview:history", history);
|
||||
solidcraft_ui_registry::publish_rect("prefs:layout_preview:history", history);
|
||||
}
|
||||
if let Some(history) = history.filter(|_| state.history == 1) {
|
||||
// A single vertical mark reads as the narrow icon-only history rail.
|
||||
@@ -279,8 +279,8 @@ pub(crate) fn paint_layout(ui: &mut egui::Ui, state: super::preferences::LayoutP
|
||||
(120.0 - inset - bw, top, (viewport_bottom - top) * 0.65)
|
||||
};
|
||||
let dialog = panel(dialog_x, dialog_top, bw, dialog_height, "Dialogs", t.dialog_footer);
|
||||
crate::scenario::publish_rect("prefs:layout_preview:dialog", dialog);
|
||||
crate::scenario::publish_rect("prefs:layout_preview", outer);
|
||||
solidcraft_ui_registry::publish_rect("prefs:layout_preview:dialog", dialog);
|
||||
solidcraft_ui_registry::publish_rect("prefs:layout_preview", outer);
|
||||
ui.interact(outer, ui.make_persistent_id("layout_preview_help"), egui::Sense::hover())
|
||||
.backing_on_hover_text(crate::i18n::tr("prefs.layout_note"));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use egui::{FontId, Rect, Sense, Stroke, Ui, pos2, vec2};
|
||||
pub(crate) fn settings_mouse_mapping(ui: &mut Ui, bindings: &[(Vec<String>, String)], mapping: impl FnOnce(&mut Ui)) {
|
||||
let keys: Vec<Vec<&str>> = bindings.iter().map(|(keys, _)| keys.iter().map(String::as_str).collect()).collect();
|
||||
let rows: Vec<(&[&str], &str)> = bindings.iter().zip(&keys).map(|((_, action), keys)| (keys.as_slice(), action.as_str())).collect();
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
mapping(ui);
|
||||
super::settings_bindings(ui, &rows);
|
||||
return;
|
||||
@@ -59,7 +59,7 @@ fn axis_columns(rect: Rect) -> [Rect; 5] {
|
||||
}
|
||||
|
||||
pub(crate) fn settings_axis_header(ui: &mut Ui) {
|
||||
if crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -96,7 +96,7 @@ pub(crate) fn settings_axis_row(
|
||||
super::layout::sized_row(ui, name, height, |ui| {
|
||||
// The row already owns these bounds; its centered cursor is not the top edge.
|
||||
let rect = ui.max_rect();
|
||||
crate::scenario::publish_rect(&format!("prefs:axis:{name}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:axis:{name}"), rect);
|
||||
t.secondary_hline(ui.painter(), rect.x_range(), rect.bottom(), t.dialog_rule);
|
||||
if narrow {
|
||||
let label_rect = Rect::from_min_size(rect.min, vec2(rect.width() * 0.5, line));
|
||||
@@ -173,11 +173,10 @@ pub(crate) fn live_axis_bar_in(ui: &Ui, live_rect: Rect, value: f32, dead_zone:
|
||||
|
||||
/// Compact per-axis gain uses a consistent two-decimal specimen and the real accepted range.
|
||||
pub(crate) fn settings_axis_gain(ui: &mut Ui, gain: &mut f32) -> egui::Response {
|
||||
if crate::prefs_search::querying(ui) && (!ui.is_visible() || crate::prefs_search::suppress_chrome(ui)) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
if super::settings_search::querying(ui) && (!ui.is_visible() || super::settings_search::suppress_chrome(ui)) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let response =
|
||||
ui.add(egui::DragValue::new(gain).range(0.0..=3.0).speed(0.05).suffix("×").fixed_decimals(2).custom_parser(crate::prefs::parse_number));
|
||||
let response = ui.add(egui::DragValue::new(gain).range(0.0..=3.0).speed(0.05).suffix("×").fixed_decimals(2).custom_parser(super::parse_number));
|
||||
response.widget_info(|| {
|
||||
let mut info = egui::WidgetInfo::drag_value(ui.is_enabled(), f64::from(*gain));
|
||||
info.label = Some(super::layout::label(ui).to_owned());
|
||||
|
||||
@@ -75,14 +75,14 @@ pub(crate) fn settings_profile_button(ui: &mut Ui, label: &str, name: &str, edit
|
||||
let value = Rect::from_min_max(pos2(text_left, overline.bottom()), pos2(text_right, rect.center().y + t.space_16));
|
||||
super::text::paint_text(ui, overline, &label.to_uppercase(), crate::fonts::semibold(t.font_panel), t.form_dim, true);
|
||||
super::text::paint_text(ui, value, name, crate::fonts::semibold(t.font_label), t.form_ink, false);
|
||||
crate::scenario::publish_rect("prefs:profile:icon", icon);
|
||||
crate::scenario::publish_rect("prefs:profile:label", overline);
|
||||
crate::scenario::publish_rect("prefs:profile:name", value);
|
||||
crate::scenario::publish_rect("prefs:profile:chevron", chevron);
|
||||
solidcraft_ui_registry::publish_rect("prefs:profile:icon", icon);
|
||||
solidcraft_ui_registry::publish_rect("prefs:profile:label", overline);
|
||||
solidcraft_ui_registry::publish_rect("prefs:profile:name", value);
|
||||
solidcraft_ui_registry::publish_rect("prefs:profile:chevron", chevron);
|
||||
if edited {
|
||||
let dot = Rect::from_min_size(pos2(text_right + t.space_6, value.center().y - t.space_4), vec2(t.space_8, t.space_8));
|
||||
let response = ui.scope_builder(egui::UiBuilder::new().max_rect(dot), |ui| settings_change_dot(ui, changed_hint)).inner;
|
||||
crate::scenario::publish_target("prefs:profile:edited", response.rect);
|
||||
solidcraft_ui_registry::publish_target("prefs:profile:edited", response.rect);
|
||||
}
|
||||
});
|
||||
let response = ui.interact(card.response.rect, ui.make_persistent_id("profile_card"), Sense::click());
|
||||
@@ -101,7 +101,7 @@ pub(crate) fn settings_profile_button(ui: &mut Ui, label: &str, name: &str, edit
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), name));
|
||||
crate::scenario::publish_rect("prefs:profile:sidebar_card", response.rect);
|
||||
solidcraft_ui_registry::publish_rect("prefs:profile:sidebar_card", response.rect);
|
||||
response.backing_on_hover_text(label)
|
||||
}
|
||||
|
||||
@@ -141,9 +141,9 @@ pub(crate) struct SettingsChip<'a> {
|
||||
|
||||
/// Bounded informational pill; an optional removal target lives inside the same outline.
|
||||
pub(crate) fn settings_chip(ui: &mut Ui, label: &str, chip: SettingsChip<'_>) -> egui::Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[label]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[label]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[label]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[label]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let ink = if chip.accent { t.selected_ink } else { t.form_dim };
|
||||
@@ -161,8 +161,8 @@ pub(crate) fn settings_chip(ui: &mut Ui, label: &str, chip: SettingsChip<'_>) ->
|
||||
let height = if chip.compact { t.space_24 - t.space_2 } else { t.space_24 };
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(natural.min(super::metrics::dimension(ui.max_rect().width())), height), Sense::hover());
|
||||
if super::layout::label(ui).is_empty() {
|
||||
crate::prefs_search::remember(ui, label, rect);
|
||||
crate::prefs_search::follow(ui, label, rect);
|
||||
super::settings_search::remember(ui, label, rect);
|
||||
super::settings_search::follow(ui, label, rect);
|
||||
}
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
@@ -212,15 +212,15 @@ pub(crate) fn settings_block_hint<'a, R>(
|
||||
}
|
||||
|
||||
fn settings_block_content<R>(ui: &mut Ui, label: &str, hint: Option<&str>, label_width: f32, add: impl FnOnce(&mut Ui) -> R) -> R {
|
||||
if let Some(gate) = crate::prefs_search::block_gate(ui, label, hint) {
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
if let Some(gate) = super::settings_search::block_gate(ui, label, hint) {
|
||||
let before = super::settings_search::hits(ui);
|
||||
if !gate.show {
|
||||
let result = crate::prefs_search::run_hidden(ui, label, add);
|
||||
crate::prefs_search::block_finish(ui, &gate, false, before);
|
||||
let result = super::settings_search::run_hidden(ui, label, add);
|
||||
super::settings_search::block_finish(ui, &gate, false, before);
|
||||
return result;
|
||||
}
|
||||
let result = settings_block_body(ui, label, hint, label_width, add);
|
||||
crate::prefs_search::block_finish(ui, &gate, true, before);
|
||||
super::settings_search::block_finish(ui, &gate, true, before);
|
||||
return result;
|
||||
}
|
||||
settings_block_body(ui, label, hint, label_width, add)
|
||||
@@ -255,7 +255,7 @@ fn settings_block_body<R>(ui: &mut Ui, label: &str, hint: Option<&str>, label_wi
|
||||
)
|
||||
.inner
|
||||
});
|
||||
crate::scenario::publish_rect(&format!("dialog:row:{label}"), response.response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:row:{label}"), response.response.rect);
|
||||
response.inner
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ pub(crate) fn settings_autosave_timeline(ui: &mut Ui, minutes: f64) {
|
||||
let (r, _) = ui.allocate_exact_size(vec2(super::metrics::dimension(ui.available_width()), t.space_24 + t.space_12 + t.space_2), Sense::hover());
|
||||
let line = r;
|
||||
if settings_page_active(ui) {
|
||||
crate::scenario::publish_rect("prefs:autosave_timeline", r);
|
||||
solidcraft_ui_registry::publish_rect("prefs:autosave_timeline", r);
|
||||
}
|
||||
ui.painter().hline(line.x_range(), r.top() + t.space_10, Stroke::new(2.0, t.dialog_rule));
|
||||
if minutes.is_finite() && minutes > 0.0 {
|
||||
@@ -324,11 +324,11 @@ pub(crate) fn settings_bindings(ui: &mut Ui, bindings: &[(&[&str], &str)]) {
|
||||
})
|
||||
.fold(t.space_24 * 8.0 + t.space_8, f32::max);
|
||||
for (keys, action) in bindings {
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::shows_all(ui) {
|
||||
if !crate::prefs_search::texts_hit(ui, &[action, &keys.join(" ")]) {
|
||||
if super::settings_search::querying(ui) && !super::settings_search::shows_all(ui) {
|
||||
if !super::settings_search::texts_hit(ui, &[action, &keys.join(" ")]) {
|
||||
continue;
|
||||
}
|
||||
crate::prefs_search::reveal(ui);
|
||||
super::settings_search::reveal(ui);
|
||||
}
|
||||
let width = super::metrics::dimension(ui.available_width());
|
||||
let action_width = super::ContentWidth::label(ui, action);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Analytic rounded annular sectors, following the board's offset-and-round construction.
|
||||
use crate::{context_menu::Item, theme::Tokens};
|
||||
use super::Item;
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Align2, Color32, FontId, Pos2, Rect, Sense, Stroke, Ui, Vec2, vec2};
|
||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU};
|
||||
|
||||
@@ -110,7 +111,7 @@ pub(crate) fn radial_menu(ui: &mut Ui, center: Pos2, items: &[Item], hot: Option
|
||||
let at = center + unit(a) * 104.0;
|
||||
let target = Rect::from_center_size(at, vec2(t.space_24 * 2.0 + t.space_12, t.space_24 + t.space_16 + t.space_2));
|
||||
let response = ui.interact(target, ui.scope_id().with(("radial", k)), Sense::hover());
|
||||
crate::scenario::publish_target(&format!("radial:{}", item.id), target);
|
||||
solidcraft_ui_registry::publish_target(&format!("radial:{}", item.id), target);
|
||||
let ink = if lit { accent.ink } else { t.form_ink };
|
||||
crate::icons::paint_accented(
|
||||
&painter,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! The colours picked last in any colour field, newest first (#9234). The app keeps the list in
|
||||
//! its saved settings (`UiState::recent_colors`) and hands it to the colour pickers every frame
|
||||
//! through egui memory; the shared `ui_kit` colour popover shows it and adds finished picks.
|
||||
//! The colours picked last in any colour field, newest first (#9234). The host keeps the list in
|
||||
//! its saved settings and hands it to the colour pickers every frame through egui memory with
|
||||
//! [`share`]; the kit's colour popover shows it and adds finished picks.
|
||||
|
||||
/// How many colours the recent list keeps.
|
||||
pub(crate) const MAX_RECENT_COLORS: usize = 12;
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Read-only report and library containers, sharing the form's typography and actions.
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Ui, vec2};
|
||||
|
||||
/// Bordered list with a bounded scroll viewport. Tokens: dialog_rule, radius_md, space_10.
|
||||
@@ -13,8 +13,8 @@ pub(crate) fn report_list<R>(ui: &mut Ui, key: &str, height: f32, add: impl FnOn
|
||||
.auto_shrink([false, true])
|
||||
.max_height(height)
|
||||
.show(ui, add);
|
||||
scenario::publish_rect(&format!("dialog:list:{key}:viewport"), output.inner_rect);
|
||||
scenario::publish_count(&format!("dialog:list:{key}:content_width"), f64::from(output.content_size.x));
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:list:{key}:viewport"), output.inner_rect);
|
||||
solidcraft_ui_registry::publish_count(&format!("dialog:list:{key}:content_width"), f64::from(output.content_size.x));
|
||||
output.inner
|
||||
})
|
||||
}
|
||||
@@ -26,7 +26,7 @@ pub(crate) fn report_divider(ui: &mut Ui, key: &str) {
|
||||
let rect = rect.expand2(vec2(t.space_10, 0.0));
|
||||
let clip = ui.clip_rect().expand2(vec2(t.space_10, 0.0));
|
||||
ui.painter().with_clip_rect(clip).hline(rect.x_range(), rect.top(), t.tertiary_border(t.dialog_rule));
|
||||
scenario::publish_rect(&format!("dialog:divider:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:divider:{key}"), rect);
|
||||
}
|
||||
|
||||
/// List border around a caller-owned virtualised viewport. Tokens: dialog_rule, control_radius, space_10.
|
||||
@@ -49,7 +49,7 @@ pub(super) fn list_frame_filled<R>(ui: &mut Ui, key: &str, fill: egui::Color32,
|
||||
add(ui)
|
||||
},
|
||||
);
|
||||
scenario::publish_rect(&format!("dialog:list:{key}"), response.response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:list:{key}"), response.response.rect);
|
||||
response.inner
|
||||
}
|
||||
|
||||
@@ -70,18 +70,18 @@ pub(crate) fn path_chip(ui: &mut Ui, path: &str, add: impl FnOnce(&mut Ui)) {
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), path));
|
||||
response.backing_on_hover_text(path);
|
||||
let key = format!("dialog:cell:{path}");
|
||||
scenario::publish_handle(&key, rect.center());
|
||||
scenario::publish_rect(&key, rect);
|
||||
solidcraft_ui_registry::publish_handle(&key, rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&key, rect);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Empty-list dashed outline and centred caption. Retains the note rectangle.
|
||||
pub(crate) fn empty_box(ui: &mut Ui, key: &str, message: &str) {
|
||||
if crate::prefs_search::querying(ui) {
|
||||
if crate::prefs_search::texts_hit(ui, &[message]) {
|
||||
crate::prefs_search::reveal(ui);
|
||||
} else if !ui.is_visible() || crate::prefs_search::suppress_chrome(ui) {
|
||||
if super::settings_search::querying(ui) {
|
||||
if super::settings_search::texts_hit(ui, &[message]) {
|
||||
super::settings_search::reveal(ui);
|
||||
} else if !ui.is_visible() || super::settings_search::suppress_chrome(ui) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ pub(crate) fn empty_box(ui: &mut Ui, key: &str, message: &str) {
|
||||
}
|
||||
ui.painter().extend(egui::Shape::dashed_line(&points, t.control_border_stroke(t.control_border), t.space_4, t.space_3));
|
||||
ui.painter().text(rect.center(), egui::Align2::CENTER_CENTER, message, egui::FontId::proportional(t.font_label), t.form_dim);
|
||||
scenario::publish_rect(&format!("dialog:note:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:note:{key}"), rect);
|
||||
}
|
||||
|
||||
/// Kind/count above UTC time, alongside actions. The legacy combined-title cell remains an alias.
|
||||
@@ -138,8 +138,8 @@ pub(crate) fn report_header(ui: &mut Ui, kind: &str, count: u64, time: &str, ali
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), alias));
|
||||
response.backing_on_hover_text(alias);
|
||||
let key = format!("dialog:cell:{alias}");
|
||||
scenario::publish_rect(&key, rect);
|
||||
scenario::publish_handle(&key, rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&key, rect);
|
||||
solidcraft_ui_registry::publish_handle(&key, rect.center());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::scenario;
|
||||
use crate::ui_kit::{self, test_support as harness};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -65,6 +65,6 @@ impl ReviewAndApply<'_> {
|
||||
});
|
||||
}
|
||||
let ack = checkbox(ui, self.acknowledgement, acknowledged);
|
||||
crate::scenario::publish_target("render:review:acknowledge", ack.rect);
|
||||
solidcraft_ui_registry::publish_target("render:review:acknowledge", ack.rect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,9 +122,9 @@ impl CommandSearch {
|
||||
// Closed with less room than its hint takes (a narrow bar): just the magnifier.
|
||||
if !open && progress == 0.0 && r.width() + 0.5 < Self::closed_width(ui.ctx()) {
|
||||
icons::paint_icon(ui.painter(), Rect::from_center_size(r.center(), vec2(t.space_16, t.space_16)), "zoom", 1.0);
|
||||
crate::scenario::publish_target("ribbon:search", r);
|
||||
crate::scenario::publish_target("shortcut:search", r);
|
||||
crate::scenario::publish_target("palette:search", r);
|
||||
solidcraft_ui_registry::publish_target("ribbon:search", r);
|
||||
solidcraft_ui_registry::publish_target("shortcut:search", r);
|
||||
solidcraft_ui_registry::publish_target("palette:search", r);
|
||||
return ui
|
||||
.interact(r, egui::Id::unique("sc_sbox_compact"), Sense::click())
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Search commands"));
|
||||
@@ -169,9 +169,9 @@ impl CommandSearch {
|
||||
if ui.rect_contains_pointer(r) {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Text);
|
||||
}
|
||||
crate::scenario::publish_target("ribbon:search", r);
|
||||
crate::scenario::publish_target("shortcut:search", r);
|
||||
crate::scenario::publish_target("palette:search", r);
|
||||
solidcraft_ui_registry::publish_target("ribbon:search", r);
|
||||
solidcraft_ui_registry::publish_target("shortcut:search", r);
|
||||
solidcraft_ui_registry::publish_target("palette:search", r);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -239,8 +239,8 @@ impl CommandSearch {
|
||||
ui.painter().rect_stroke(sheet, t.radius_xl, frame.stroke, egui::StrokeKind::Inside);
|
||||
super::Container::paint_inset(ui.painter(), sheet, frame);
|
||||
ui.allocate_rect(sheet, Sense::hover());
|
||||
crate::scenario::publish_target("ribbon:search_popup", sheet);
|
||||
crate::scenario::publish_target("palette:outer", sheet);
|
||||
solidcraft_ui_registry::publish_target("ribbon:search_popup", sheet);
|
||||
solidcraft_ui_registry::publish_target("palette:outer", sheet);
|
||||
sheet
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ impl CommandSearch {
|
||||
let frame = egui::Frame::NONE.fill(t.dialog_footer).corner_radius(egui::CornerRadius { nw: 0, ne: 0, sw: t.radius_xl, se: t.radius_xl });
|
||||
super::Container::set_backing(ui.painter(), background, frame, r);
|
||||
t.secondary_hline(ui.painter(), r.x_range(), r.top(), t.dialog_rule);
|
||||
crate::scenario::publish_rect("ribbon:search_footer", r);
|
||||
solidcraft_ui_registry::publish_rect("ribbon:search_footer", r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ impl super::MenuItem<'_> {
|
||||
super::text::paint_text(ui, path_rect, path, FontId::proportional(t.font_small), t.form_dim, false);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, self.enabled, label));
|
||||
crate::scenario::publish_target(&format!("menu:{}", self.id), r);
|
||||
solidcraft_ui_registry::publish_target(&format!("menu:{}", self.id), r);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Search across Preferences. The field is the Keyboard Shortcuts magnifier. A match is the
|
||||
//! control's own label, its section, its page, or the value a dropdown is showing, scored the
|
||||
//! same way as command search. English counts too, from the message catalog, so a new setting
|
||||
//! is found by being drawn — nothing is registered for search.
|
||||
//! Search across a settings page the kit draws; the Preferences window is SolidCraft's use of it.
|
||||
//! The field is the Keyboard Shortcuts magnifier. A match is the control's own label, its
|
||||
//! section, its page, or the value a dropdown is showing, scored the same way as command search.
|
||||
//! English counts too, from the message catalog, so a new setting is found by being drawn —
|
||||
//! nothing is registered for search.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
@@ -801,7 +802,7 @@ pub(crate) fn remember(ui: &mut Ui, label: &str, rect: Rect) {
|
||||
if scroll {
|
||||
ui.scroll_to_rect(rect, Some(egui::Align::Center));
|
||||
}
|
||||
crate::scenario::publish_handle("prefs:search:current", rect.center());
|
||||
solidcraft_ui_registry::publish_handle("prefs:search:current", rect.center());
|
||||
}
|
||||
|
||||
pub(crate) fn set_reveal(ctx: &Context, reveal: Option<Reveal>) {
|
||||
@@ -815,13 +816,13 @@ pub(crate) fn set_reveal(ctx: &Context, reveal: Option<Reveal>) {
|
||||
}
|
||||
|
||||
pub(crate) fn reveal_for(page: usize, label: &str) -> Reveal {
|
||||
Reveal { page, label: label.to_string(), until: crate::now_ms() + REVEAL_MS }
|
||||
Reveal { page, label: label.to_string(), until: now_ms() + REVEAL_MS }
|
||||
}
|
||||
|
||||
/// Scroll the opened setting into view and outline it until [`Reveal::until`].
|
||||
pub(crate) fn follow(ui: &Ui, label: &str, rect: Rect) {
|
||||
let Some(reveal) = ui.data(|data| data.get_temp::<Reveal>(Id::unique(REVEAL_ID))) else { return };
|
||||
if crate::now_ms() > reveal.until || fold(label) != fold(&reveal.label) {
|
||||
if now_ms() > reveal.until || fold(label) != fold(&reveal.label) {
|
||||
return;
|
||||
}
|
||||
let page = with(ui, |state| state.page_index);
|
||||
@@ -831,7 +832,13 @@ pub(crate) fn follow(ui: &Ui, label: &str, rect: Rect) {
|
||||
ui.scroll_to_rect(rect, Some(egui::Align::Center));
|
||||
let t = Tokens::get();
|
||||
ui.painter().rect_stroke(rect, t.radius_sm, egui::Stroke::new(t.space_2, t.form_primary), egui::StrokeKind::Outside);
|
||||
crate::scenario::publish_handle("prefs:revealed", rect.center());
|
||||
solidcraft_ui_registry::publish_handle("prefs:revealed", rect.center());
|
||||
}
|
||||
|
||||
/// Wall clock in milliseconds, for how long a reveal stays outlined.
|
||||
fn now_ms() -> f64 {
|
||||
use web_time::{SystemTime, UNIX_EPOCH};
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64() * 1000.0).unwrap_or(0.0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -84,7 +84,7 @@ fn search_with_icon(ui: &mut Ui, id: egui::Id, value: &mut String, hint: &str) -
|
||||
icons::paint_ink(ui.painter(), icon, "zoom", t.form_dim);
|
||||
let show_clear = !value.is_empty();
|
||||
let clear_side = if show_clear { height } else { super::CommandSearch::key(ui, r.right_center() - vec2(t.space_6, 0.0), "Ctrl F") + t.space_12 };
|
||||
crate::scenario::publish_rect("prefs:search:frame", r);
|
||||
solidcraft_ui_registry::publish_rect("prefs:search:frame", r);
|
||||
let editor =
|
||||
Rect::from_min_max(pos2((icon.right() + t.space_6).min(r.right()), r.top()), pos2((r.right() - clear_side).max(icon.right()), r.bottom()));
|
||||
let response = ui
|
||||
@@ -110,7 +110,7 @@ fn search_with_icon(ui: &mut Ui, id: egui::Id, value: &mut String, hint: &str) -
|
||||
let clear_rect = Rect::from_center_size(pos2(r.right() - height * 0.5, r.center().y), vec2(height, height));
|
||||
let clear = ui.push_id(id.with("clear"), |ui| super::quiet_icon_button_at(ui, clear_rect, "close", "Clear")).inner;
|
||||
if id == egui::Id::unique("prefs_search") {
|
||||
crate::scenario::publish_handle("prefs:search:clear", clear_rect.center());
|
||||
solidcraft_ui_registry::publish_handle("prefs:search:clear", clear_rect.center());
|
||||
}
|
||||
if clear.clicked() {
|
||||
value.clear();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Small deterministic material previews; no textures, jobs or graphics device.
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Color32, Mesh, Rect, Response, Sense, Ui, pos2, vec2};
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
@@ -171,8 +171,8 @@ pub fn sphere_thumbnail(ui: &mut Ui, id: &str, sphere: Sphere, size: f32) -> Res
|
||||
let side = f32::from(sphere.key(size).side);
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(side, side), Sense::hover());
|
||||
paint_sphere(ui, rect, sphere);
|
||||
scenario::publish_rect(id, rect);
|
||||
scenario::publish_handle(id, rect.center());
|
||||
solidcraft_ui_registry::publish_rect(id, rect);
|
||||
solidcraft_ui_registry::publish_handle(id, rect.center());
|
||||
response
|
||||
}
|
||||
|
||||
@@ -221,16 +221,16 @@ pub(crate) fn sphere_preview_with_source<R>(
|
||||
);
|
||||
source_ui.set_clip_rect(ui.clip_rect().intersect(source_rect));
|
||||
let source = source(&mut source_ui);
|
||||
scenario::publish_rect(&format!("{id}:chip:0"), source.rect.intersect(source_rect));
|
||||
solidcraft_ui_registry::publish_rect(&format!("{id}:chip:0"), source.rect.intersect(source_rect));
|
||||
let mut usage_ui = ui.new_child(
|
||||
egui::UiBuilder::new().id_salt("sample_usage").max_rect(usage_rect).layout(egui::Layout::left_to_right(egui::Align::Center)),
|
||||
);
|
||||
usage_ui.set_clip_rect(ui.clip_rect().intersect(usage_rect));
|
||||
let response = super::workspace_chip(&mut usage_ui, usage);
|
||||
scenario::publish_rect(&format!("{id}:chip:1"), response.rect.intersect(usage_rect));
|
||||
solidcraft_ui_registry::publish_rect(&format!("{id}:chip:1"), response.rect.intersect(usage_rect));
|
||||
});
|
||||
let response = ui.vertical(action);
|
||||
scenario::publish_target(&format!("{id}:replace"), response.response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{id}:replace"), response.response.rect);
|
||||
response.inner
|
||||
});
|
||||
row.inner
|
||||
@@ -291,11 +291,11 @@ impl SphereTile<'_> {
|
||||
job.wrap.break_anywhere = false;
|
||||
let galley = ui.painter().layout_job(job);
|
||||
let label_pos = pos2(label.center().x - galley.rect.center().x, label.top() - galley.rect.top());
|
||||
scenario::publish_rect(&format!("{}:label", self.id), label);
|
||||
scenario::publish_rect(&format!("{}:galley", self.id), galley.rect.translate(label_pos.to_vec2()));
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:label", self.id), label);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:galley", self.id), galley.rect.translate(label_pos.to_vec2()));
|
||||
ui.painter().with_clip_rect(ui.clip_rect().intersect(label)).galley(label_pos, galley, t.form_ink);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), self.selected, self.name));
|
||||
scenario::publish_target(self.id, rect);
|
||||
solidcraft_ui_registry::publish_target(self.id, rect);
|
||||
let mut tip = egui::Tooltip::for_enabled(&response);
|
||||
tip.popup = tip.popup.frame(super::Container::new(ui.ctx(), super::Surface::Toast).tooltip_frame(ui.ctx()));
|
||||
crate::ui_kit::Container::show_tooltip(response.id, tip, |ui| {
|
||||
|
||||
@@ -37,12 +37,12 @@ pub(crate) fn status_header(ui: &mut egui::Ui, key: &str, name: &str, status: St
|
||||
if fraction > 0.0 {
|
||||
ui.painter().rect_filled(egui::Rect::from_min_size(meter.min, vec2(meter.width() * fraction, meter.height())), t.space_3, t.form_ink);
|
||||
}
|
||||
crate::scenario::publish_rect(&format!("{key}:meter"), meter);
|
||||
crate::scenario::publish_count(&format!("{key}:constrained_fraction"), f64::from(fraction));
|
||||
solidcraft_ui_registry::publish_rect(&format!("{key}:meter"), meter);
|
||||
solidcraft_ui_registry::publish_count(&format!("{key}:constrained_fraction"), f64::from(fraction));
|
||||
})
|
||||
.response;
|
||||
t.secondary_hline(ui.painter(), ui.max_rect().x_range(), ui.cursor().top(), t.dialog_rule);
|
||||
crate::scenario::publish_target(&format!("{key}:status_header"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&format!("{key}:status_header"), response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn progress(ui: &mut egui::Ui, key: &str, fraction: f32) -> egui::Res
|
||||
ui.painter().rect_filled(egui::Rect::from_min_size(bar.min, vec2(bar.width() * fraction, bar.height())), t.space_3, t.form_primary);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::ProgressIndicator, ui.is_enabled(), format!("{:.0}%", fraction * 100.0)));
|
||||
crate::scenario::publish_rect(&format!("dialog:progress:{key}"), bar);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:progress:{key}"), bar);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -132,8 +132,8 @@ pub(crate) fn steps(ui: &mut egui::Ui, key: &str, labels: &[&str], current: usiz
|
||||
std::cmp::Ordering::Greater => "next",
|
||||
};
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), *label));
|
||||
crate::scenario::publish_rect(&format!("dialog:step:{key}:{i}:{state}"), row);
|
||||
crate::scenario::publish_rect(&format!("dialog:step_mark:{key}:{i}"), slot);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:step:{key}:{i}:{state}"), row);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:step_mark:{key}:{i}"), slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ pub(crate) fn check_status_line(ui: &mut egui::Ui, key: &str, label: &str, statu
|
||||
super::text(ui, label, super::Text::Report(key, false));
|
||||
})
|
||||
.response;
|
||||
crate::scenario::publish_rect(key, response.rect);
|
||||
solidcraft_ui_registry::publish_rect(key, response.rect);
|
||||
response
|
||||
}
|
||||
|
||||
@@ -240,14 +240,14 @@ pub(crate) fn progress_action(ui: &mut egui::Ui, key: &str, name: &str, status:
|
||||
};
|
||||
let shown = fixed(ui, name_width, &|ui| super::text(ui, name, super::Text::Inline));
|
||||
let cut = name_natural > name_width + 0.5;
|
||||
crate::scenario::publish_rect(&format!("{key}:name"), shown.rect);
|
||||
crate::scenario::publish_count(&format!("{key}:name:cut"), f64::from(u8::from(cut)));
|
||||
solidcraft_ui_registry::publish_rect(&format!("{key}:name"), shown.rect);
|
||||
solidcraft_ui_registry::publish_count(&format!("{key}:name:cut"), f64::from(u8::from(cut)));
|
||||
fixed(ui, status_width, &|ui| super::text(ui, status, super::Text::Inline));
|
||||
fixed(ui, meter, &|ui| progress(ui, key, fraction));
|
||||
// Cancel keeps its whole label whatever room the row has left.
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
let response = super::button(ui, cancel, super::Button::Normal);
|
||||
crate::scenario::publish_rect(&format!("{key}:cancel"), response.rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{key}:cancel"), response.rect);
|
||||
response
|
||||
})
|
||||
.inner
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Dense editable tables. Geometry and typography share the form label-column tokens.
|
||||
use super::metrics::*;
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Response, Ui, vec2};
|
||||
|
||||
/// A column's minimum width is a multiple of the measured form label column.
|
||||
@@ -78,8 +78,8 @@ pub(crate) fn table_custom<R>(ui: &mut Ui, id: &str, columns: &[Column<'_>], emp
|
||||
result
|
||||
},
|
||||
);
|
||||
scenario::publish_rect(&format!("dialog:table:{id}:viewport"), output.inner_rect);
|
||||
scenario::publish_count(&format!("dialog:table:{id}:content_width"), f64::from(output.content_size.x));
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:table:{id}:viewport"), output.inner_rect);
|
||||
solidcraft_ui_registry::publish_count(&format!("dialog:table:{id}:content_width"), f64::from(output.content_size.x));
|
||||
// The nested horizontal ScrollArea consumes vertical scroll targets too. Request the
|
||||
// reveal here so it reaches the card's vertical scroller, only after an explicit action.
|
||||
if let Some(rect) = reveal {
|
||||
@@ -165,7 +165,7 @@ impl Table<'_> {
|
||||
}
|
||||
},
|
||||
);
|
||||
scenario::publish_rect(&format!("dialog:column:{}:{}", self.id, column.label), cell);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:column:{}:{}", self.id, column.label), cell);
|
||||
left = cell.right() + t.space_8;
|
||||
}
|
||||
ui.painter().hline(rect.x_range(), rect.bottom(), t.tertiary_border(t.dialog_rule));
|
||||
@@ -217,8 +217,8 @@ impl Table<'_> {
|
||||
false,
|
||||
);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), crate::i18n::tr_label(label)));
|
||||
scenario::publish_handle(&format!("dialog:table:{}:add", self.id), rect.center());
|
||||
scenario::publish_rect(&format!("dialog:table:{}:add", self.id), rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:table:{}:add", self.id), rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:table:{}:add", self.id), rect);
|
||||
response.on_hover_cursor(egui::CursorIcon::PointingHand).backing_on_hover_text(crate::i18n::tr_label(tooltip))
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ impl Table<'_> {
|
||||
ui.painter().rect_filled(rect, t.control_radius, if selected { t.selected_bg } else { t.hover });
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), selected, key));
|
||||
scenario::publish_rect(&format!("dialog:table:{}:{key}", self.id), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:table:{}:{key}", self.id), rect);
|
||||
let ppp = ui.ctx().pixels_per_point().max(0.5);
|
||||
let left = (rect.left() * ppp).round() / ppp + t.space_8;
|
||||
ui.push_id((&self.id, key), |ui| {
|
||||
@@ -267,7 +267,7 @@ impl TableRow<'_> {
|
||||
let width = self.widths.get(self.column).copied().unwrap_or(0.0);
|
||||
let label = self.columns.get(self.column).map_or("", |c| c.label);
|
||||
let rect = egui::Rect::from_min_size(egui::pos2(self.left, self.rect.top()), vec2(width, self.rect.height()));
|
||||
scenario::publish_rect(&format!("{}:cell:{label}", self.id), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("{}:cell:{label}", self.id), rect);
|
||||
self.column += 1;
|
||||
self.left += width + Tokens::get().space_8;
|
||||
self.ui
|
||||
@@ -413,8 +413,8 @@ pub(crate) fn removable_header(ui: &mut Ui, label: &str) -> bool {
|
||||
return false;
|
||||
}
|
||||
let response = super::actions::icon_button_colored(ui, button, "close", "Remove the column", Some(t.form_error));
|
||||
scenario::publish_handle(&format!("dialog:button:{label}:×"), button.center());
|
||||
scenario::publish_rect(&format!("dialog:button:{label}:×"), button);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:button:{label}:×"), button.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:button:{label}:×"), button);
|
||||
response.clicked()
|
||||
}
|
||||
|
||||
@@ -427,8 +427,8 @@ pub(crate) fn bin_button(ui: &mut Ui, tooltip: &str) -> Response {
|
||||
[("Actions", "Delete"), ("Actions", "×"), (super::layout::label(ui), crate::i18n::tr_label("Delete")), (super::layout::label(ui), "×")]
|
||||
{
|
||||
let key = format!("dialog:button:{row}:{label}");
|
||||
scenario::publish_handle(&key, rect.center());
|
||||
scenario::publish_rect(&key, rect);
|
||||
solidcraft_ui_registry::publish_handle(&key, rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&key, rect);
|
||||
}
|
||||
response
|
||||
}
|
||||
@@ -447,8 +447,8 @@ pub(crate) fn plus_header<R>(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> Opt
|
||||
ui.painter().vline(x, rect.y_range(), egui::Stroke::new(t.space_2, t.form_primary));
|
||||
}
|
||||
}
|
||||
scenario::publish_handle("dialog:menu:Add Column", rect.center());
|
||||
scenario::publish_rect("dialog:menu:Add Column", rect);
|
||||
solidcraft_ui_registry::publish_handle("dialog:menu:Add Column", rect.center());
|
||||
solidcraft_ui_registry::publish_rect("dialog:menu:Add Column", rect);
|
||||
let popup = Container::new(ui.ctx(), Surface::Popup).menu(&response).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside);
|
||||
let open = popup.is_open();
|
||||
let result = popup.show(|ui| super::scope(ui, add)).map(|r| r.inner);
|
||||
@@ -467,8 +467,8 @@ pub(crate) fn active_pill(ui: &mut Ui) {
|
||||
ui.painter().rect(rect, t.radius_xl, t.control_fill, t.control_border_stroke(t.control_border), egui::StrokeKind::Inside);
|
||||
super::text::paint_text(ui, rect.shrink2(vec2(t.space_8, 0.0)), label, crate::fonts::semibold(t.font_small), t.form_primary, false);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Label, ui.is_enabled(), label));
|
||||
scenario::publish_handle(&format!("dialog:inline:{label}"), rect.center());
|
||||
scenario::publish_rect(&format!("dialog:inline:{label}"), rect);
|
||||
solidcraft_ui_registry::publish_handle(&format!("dialog:inline:{label}"), rect.center());
|
||||
solidcraft_ui_registry::publish_rect(&format!("dialog:inline:{label}"), rect);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::scenario;
|
||||
use crate::ui_kit::{self, test_support as harness};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -249,7 +249,7 @@ pub(crate) fn menu(ui: &Ui, response: &mut Response, text: &mut dyn TextBuffer,
|
||||
};
|
||||
let mut picked = None;
|
||||
let open = super::Menu::context(response, |ui| {
|
||||
super::Menu::show(ui, crate::context_menu::LIST_W, |ui| {
|
||||
super::Menu::show(ui, super::LIST_W, |ui| {
|
||||
for edit in Edit::ALL {
|
||||
let (row, message, icon, key) = edit.row();
|
||||
let shortcut = crate::keymap::display(key);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Shared theme browser vocabulary: toggle segments, slots, marked cards and scroll viewport.
|
||||
use super::*;
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Align2, Color32, FontId, Painter, Rect, Response, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
@@ -121,8 +121,8 @@ impl<'a> ThemeSegment<'a> {
|
||||
|
||||
pub(crate) fn theme_segments(ui: &mut Ui, key: &str, options: &[ThemeSegment<'_>], selected: Option<usize>, icon_only: bool) -> Option<usize> {
|
||||
let labels: Vec<_> = options.iter().map(|o| o.label).collect();
|
||||
crate::prefs_search::note_unlabelled(ui, &labels);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &labels) {
|
||||
super::settings_search::note_unlabelled(ui, &labels);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &labels) {
|
||||
return None;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
@@ -188,11 +188,11 @@ pub(crate) fn theme_segments(ui: &mut Ui, key: &str, options: &[ThemeSegment<'_>
|
||||
ui.painter().rect_stroke(r.expand(t.space_2), rounding, Stroke::new(t.space_2, t.form_primary), StrokeKind::Outside);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), on, option.label));
|
||||
crate::prefs_search::remember(ui, option.label, r);
|
||||
crate::prefs_search::follow(ui, option.label, r);
|
||||
super::settings_search::remember(ui, option.label, r);
|
||||
super::settings_search::follow(ui, option.label, r);
|
||||
let visible = r.intersect(ui.clip_rect());
|
||||
if visible.is_positive() {
|
||||
scenario::publish_target(&format!("prefs:theme_{key}:{index}"), visible);
|
||||
solidcraft_ui_registry::publish_target(&format!("prefs:theme_{key}:{index}"), visible);
|
||||
}
|
||||
if response.clicked() {
|
||||
clicked = Some(index);
|
||||
@@ -239,7 +239,7 @@ pub(crate) fn theme_toolbar<L, R>(
|
||||
let b = trailing(&mut right);
|
||||
let height = if stacked { left_height + gap + right.min_size().y } else { left_height.max(right.min_size().y) };
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(width, height), Sense::hover());
|
||||
scenario::publish_rect(&format!("prefs:theme_toolbar:{key}"), rect);
|
||||
solidcraft_ui_registry::publish_rect(&format!("prefs:theme_toolbar:{key}"), rect);
|
||||
(a, b)
|
||||
}
|
||||
|
||||
@@ -249,9 +249,9 @@ fn bounded_width(ui: &Ui) -> f32 {
|
||||
}
|
||||
|
||||
pub(crate) fn theme_import_button(ui: &mut Ui, label: &str) -> Response {
|
||||
crate::prefs_search::note_unlabelled(ui, &[label]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[label]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[label]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[label]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let width = ContentWidth::name(ui, label) + t.space_14 + t.space_6 + t.space_10 * 2.0;
|
||||
@@ -270,8 +270,8 @@ pub(crate) fn theme_import_button(ui: &mut Ui, label: &str) -> Response {
|
||||
ui.painter().rect_stroke(r.expand(t.space_2), t.radius_md, Stroke::new(t.space_2, t.form_primary), StrokeKind::Outside);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), label));
|
||||
crate::prefs_search::remember(ui, label, r);
|
||||
crate::prefs_search::follow(ui, label, r);
|
||||
super::settings_search::remember(ui, label, r);
|
||||
super::settings_search::follow(ui, label, r);
|
||||
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
@@ -286,9 +286,9 @@ pub(crate) struct ThemeSlot<'a> {
|
||||
}
|
||||
pub(crate) fn theme_slot(ui: &mut Ui, slot: &ThemeSlot<'_>) -> Response {
|
||||
let t = Tokens::get();
|
||||
crate::prefs_search::note_unlabelled(ui, &[slot.title, slot.name, slot.hint]);
|
||||
if crate::prefs_search::querying(ui) && !crate::prefs_search::visible(ui, &[slot.title, slot.name, slot.hint]) {
|
||||
return crate::prefs_search::hidden_response(ui);
|
||||
super::settings_search::note_unlabelled(ui, &[slot.title, slot.name, slot.hint]);
|
||||
if super::settings_search::querying(ui) && !super::settings_search::visible(ui, &[slot.title, slot.name, slot.hint]) {
|
||||
return super::settings_search::hidden_response(ui);
|
||||
}
|
||||
let (r, response) = ui.allocate_exact_size(vec2(slot.width.max(0.0), t.space_24 * 2.0 + t.space_16), Sense::click());
|
||||
ui.painter().rect(
|
||||
@@ -308,8 +308,8 @@ pub(crate) fn theme_slot(ui: &mut Ui, slot: &ThemeSlot<'_>) -> Response {
|
||||
p.text(pos2(x, r.bottom() - t.space_16 - t.space_4), Align2::LEFT_CENTER, slot.name, crate::fonts::semibold(t.font_label), t.form_ink);
|
||||
p.text(pos2(r.right() - t.space_10, r.center().y), Align2::RIGHT_CENTER, slot.hint, FontId::proportional(t.font_small), t.form_dim);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), slot.aimed, slot.title));
|
||||
crate::prefs_search::remember(ui, slot.title, r);
|
||||
crate::prefs_search::follow(ui, slot.title, r);
|
||||
super::settings_search::remember(ui, slot.title, r);
|
||||
super::settings_search::follow(ui, slot.title, r);
|
||||
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ pub(crate) fn theme_empty(ui: &mut Ui, message: &str, clear: &str, reset: impl F
|
||||
reset();
|
||||
}
|
||||
});
|
||||
scenario::publish_rect("prefs:theme_empty", ui.min_rect());
|
||||
solidcraft_ui_registry::publish_rect("prefs:theme_empty", ui.min_rect());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ pub(crate) fn theme_browser(ui: &mut Ui, id: &str, count: usize, grid: bool, rev
|
||||
offset = out.state.clamped_offset().x;
|
||||
let r = out.inner_rect;
|
||||
let hovered = ui.rect_contains_pointer(r);
|
||||
let wheel = if hovered { crate::navigation::take_horizontal_wheel(ui, offset, max) } else { 0.0 };
|
||||
let wheel = if hovered { super::wheel::take_horizontal_wheel(ui, offset, max) } else { 0.0 };
|
||||
let horizontal_wheel = wheel != 0.0;
|
||||
if horizontal_wheel {
|
||||
offset = (offset - wheel).clamp(0.0, max);
|
||||
@@ -532,7 +532,7 @@ pub(crate) fn theme_browser(ui: &mut Ui, id: &str, count: usize, grid: bool, rev
|
||||
}
|
||||
let label = crate::i18n::tr(if left { "prefs.appearance.previous_themes" } else { "prefs.appearance.next_themes" });
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), label));
|
||||
scenario::publish_target(if left { "prefs:theme_previous" } else { "prefs:theme_next" }, arrow);
|
||||
solidcraft_ui_registry::publish_target(if left { "prefs:theme_previous" } else { "prefs:theme_next" }, arrow);
|
||||
if response.clicked() {
|
||||
// A screenful: the cards that fit in view, at least one (#11434).
|
||||
let page = (r.width() / stride).floor().max(1.0) * stride;
|
||||
@@ -544,6 +544,6 @@ pub(crate) fn theme_browser(ui: &mut Ui, id: &str, count: usize, grid: bool, rev
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
ui.data_mut(|d| d.insert_temp(key, offset));
|
||||
scenario::publish_rect("prefs:theme_viewport", r);
|
||||
solidcraft_ui_registry::publish_rect("prefs:theme_viewport", r);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
let mut x = r.left() + if row.compact { t.space_8 + row.depth as f32 * t.space_14 } else { 2.0 + row.depth as f32 * INDENT };
|
||||
let cy = r.top() + row_height / 2.0;
|
||||
// Where the row's depth puts it, whatever controls it has.
|
||||
crate::scenario::publish_handle(&format!("indent:{}", row.label), pos2(x, cy));
|
||||
solidcraft_ui_registry::publish_handle(&format!("indent:{}", row.label), pos2(x, cy));
|
||||
let action_width = if row.compact { t.space_16 } else { t.form_close_size };
|
||||
let action_left = (r.right() - if row.compact { t.space_8 } else { t.space_4 } - action_width * row.actions.len() as f32).max(r.left());
|
||||
for (index, action) in row.actions.iter().enumerate() {
|
||||
@@ -198,9 +198,9 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
Some(reason) => response.backing_on_disabled_hover_text(format!("{}\n{reason}", action.label)),
|
||||
None => response.backing_on_hover_text(action.label),
|
||||
};
|
||||
crate::scenario::publish_rect(action.target, rect);
|
||||
solidcraft_ui_registry::publish_rect(action.target, rect);
|
||||
if action.reason.is_none() {
|
||||
crate::scenario::publish_target(action.target, rect);
|
||||
solidcraft_ui_registry::publish_target(action.target, rect);
|
||||
}
|
||||
out.actions.push(response);
|
||||
}
|
||||
@@ -249,7 +249,7 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
crate::ui_kit::disclosure(ui.painter(), pos2(x + 6.0, cy), open, if hover_on == 1 { t.text } else { t.text_dim });
|
||||
out.fold = resp.clicked() && click_on == 1;
|
||||
out.fold_at = Some(fold_r.center());
|
||||
crate::scenario::publish_handle(&format!("fold:{}", row.label), fold_r.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("fold:{}", row.label), fold_r.center());
|
||||
}
|
||||
x += if row.compact { t.space_16 } else { 14.0 };
|
||||
// Eye.
|
||||
@@ -281,7 +281,7 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
}
|
||||
out.eye_rect = Some(eye_r.intersect(ui.clip_rect()));
|
||||
out.eye_at = Some(er.center());
|
||||
crate::scenario::publish_handle(&format!("eye:{}", row.label), er.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("eye:{}", row.label), er.center());
|
||||
}
|
||||
if !row.compact {
|
||||
x += slot;
|
||||
@@ -332,7 +332,7 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
right -= t.space_16;
|
||||
let sr = Rect::from_center_size(pos2(right + t.space_8, cy), vec2(t.space_14, t.space_14));
|
||||
icons::paint_icon(ui.painter(), sr, status.icon(), 1.0);
|
||||
crate::scenario::publish_handle(&format!("status:{}:{}", status.icon(), row.label), sr.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("status:{}:{}", status.icon(), row.label), sr.center());
|
||||
right -= t.space_4;
|
||||
}
|
||||
for text in [row.count, row.secondary, if row.compact { row.subtitle } else { "" }] {
|
||||
@@ -367,7 +367,10 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
let galley = ui.painter().layout_job(job);
|
||||
let lw = galley.size().x;
|
||||
let elided = galley.elided;
|
||||
crate::scenario::publish_rect(&format!("row_label:{}", row.label), Rect::from_min_size(pos2(x, cy - galley.size().y / 2.0), galley.size()));
|
||||
solidcraft_ui_registry::publish_rect(
|
||||
&format!("row_label:{}", row.label),
|
||||
Rect::from_min_size(pos2(x, cy - galley.size().y / 2.0), galley.size()),
|
||||
);
|
||||
ui.painter().with_clip_rect(ui.painter().clip_rect().intersect(r)).galley(pos2(x, cy - galley.size().y / 2.0), galley, col);
|
||||
if row.cut {
|
||||
let inside = r.shrink2(vec2(t.space_2, t.space_2));
|
||||
@@ -396,9 +399,9 @@ pub(crate) fn tree_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
let ir = Rect::from_center_size(action_r.center(), vec2(t.space_14, t.space_14));
|
||||
icons::paint_icon(ui.painter(), ir, icon, if action_hover { 1.0 } else { 0.6 });
|
||||
out.action = resp.clicked() && action_click;
|
||||
crate::scenario::publish_handle(&format!("action:{}", row.label), action_r.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("action:{}", row.label), action_r.center());
|
||||
}
|
||||
crate::scenario::publish_handle(&format!("row:{}", row.label), r.center());
|
||||
solidcraft_ui_registry::publish_handle(&format!("row:{}", row.label), r.center());
|
||||
let tip = match hover_on {
|
||||
0 if action_hover => row.action.map(|(_, tip)| crate::i18n::tr_label(tip)),
|
||||
1 => Some(if row.fold == Some(true) { crate::i18n::tr("ui.collapse") } else { crate::i18n::tr("ui.expand") }),
|
||||
@@ -441,7 +444,7 @@ pub(crate) fn tree_header(ui: &mut egui::Ui, title: &str, lens: &mut usize, labe
|
||||
let (band, _) = ui.allocate_exact_size(vec2(ui.available_width(), t.form_header_height), Sense::hover());
|
||||
let search = Rect::from_center_size(pos2(band.right() - t.space_8 - t.space_12, band.center().y), vec2(t.space_24, t.space_24));
|
||||
let response = super::quiet_icon_button_at(ui, search, "zoom", search_label);
|
||||
crate::scenario::publish_target("render:tree:search", search);
|
||||
solidcraft_ui_registry::publish_target("render:tree:search", search);
|
||||
let lens_width = labels
|
||||
.iter()
|
||||
.map(|label| ui.painter().layout_no_wrap((*label).into(), crate::fonts::semibold(t.font_small), t.text).size().x + t.space_16 + t.space_4)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Persistent viewport status content. The caller owns items and dispatches returned actions.
|
||||
use super::{Button, Container, Surface};
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::{fonts::RichTextExt as _, scenario, theme::Tokens};
|
||||
use crate::{fonts::RichTextExt as _, theme::Tokens};
|
||||
use egui::{Id, Rect, Response, RichText, Ui, vec2};
|
||||
|
||||
/// Shape and outline communicate the kind independently of colour.
|
||||
@@ -175,8 +175,8 @@ fn icon_action(ui: &mut Ui, item: &StatusItem, part: &str, icon: &str, label: &s
|
||||
crate::icons::paint_icon(ui.painter(), glyph, icon, 1.0);
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), label));
|
||||
scenario::publish_target(&target(item, part), response.rect);
|
||||
scenario::publish_target(&target(item, &format!("{part}_glyph")), glyph);
|
||||
solidcraft_ui_registry::publish_target(&target(item, part), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, &format!("{part}_glyph")), glyph);
|
||||
response.backing_on_hover_text(hint)
|
||||
}
|
||||
|
||||
@@ -210,13 +210,13 @@ pub(crate) fn viewport_status_item(ui: &mut Ui, item: &StatusItem, state: &mut S
|
||||
let (disc_rect, _) = ui.allocate_exact_size(vec2(disc_size, disc_size), egui::Sense::hover());
|
||||
ui.painter().circle_filled(disc_rect.center(), disc_rect.width() / 2.0, disc);
|
||||
crate::icons::paint_icon(ui.painter(), disc_rect.shrink(t.space_3), item.kind.icon(), 1.0);
|
||||
scenario::publish_target(&target(item, "icon"), disc_rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "icon"), disc_rect);
|
||||
let name = ui.label(RichText::new(&item.name).size(t.font_label).semibold().color(t.text));
|
||||
scenario::publish_target(&target(item, "name"), name.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "name"), name.rect);
|
||||
if !item.setting.is_empty() {
|
||||
let text = RichText::new(&item.setting).monospace().size(t.font_small).color(t.text_dim);
|
||||
let response = if item.editable { super::button(ui, text, Button::Ghost) } else { ui.label(text) };
|
||||
scenario::publish_target(&target(item, "setting"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "setting"), response.rect);
|
||||
if item.editable {
|
||||
state.focus_ids.push(response.id);
|
||||
}
|
||||
@@ -256,7 +256,7 @@ pub(crate) fn viewport_status_item(ui: &mut Ui, item: &StatusItem, state: &mut S
|
||||
if focus.has_focus() {
|
||||
ui.painter().rect_stroke(response.rect.expand(t.space_3), frame.corner_radius, ui.visuals().selection.stroke, egui::StrokeKind::Outside);
|
||||
}
|
||||
scenario::publish_target(&target(item, "item"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "item"), response.rect);
|
||||
(response.rect, action)
|
||||
}
|
||||
|
||||
@@ -299,8 +299,8 @@ pub(crate) fn viewport_status_row(
|
||||
}
|
||||
});
|
||||
});
|
||||
scenario::publish_rect("viewport_status:row", row.response.rect);
|
||||
scenario::publish_count("viewport_status:count", items.len() as f64);
|
||||
solidcraft_ui_registry::publish_rect("viewport_status:row", row.response.rect);
|
||||
solidcraft_ui_registry::publish_count("viewport_status:count", items.len() as f64);
|
||||
if state.return_pending && state.return_focus.is_none_or(|id| !state.focus_ids.contains(&id)) {
|
||||
state.return_pending = false;
|
||||
state.return_focus = None;
|
||||
@@ -383,7 +383,10 @@ pub(crate) fn viewport_status_summary(
|
||||
if attention {
|
||||
let dot = egui::pos2(x + t.space_3, rect.center().y);
|
||||
ui.painter().circle_filled(dot, t.space_3, t.warning);
|
||||
scenario::publish_target("viewport_status:summary:attention", Rect::from_center_size(dot, vec2(t.space_6, t.space_6)));
|
||||
solidcraft_ui_registry::publish_target(
|
||||
"viewport_status:summary:attention",
|
||||
Rect::from_center_size(dot, vec2(t.space_6, t.space_6)),
|
||||
);
|
||||
x += t.space_12;
|
||||
}
|
||||
let chevron = Rect::from_center_size(egui::pos2(x + t.space_12, rect.center().y), vec2(t.space_16, t.space_16));
|
||||
@@ -397,8 +400,8 @@ pub(crate) fn viewport_status_summary(
|
||||
&[("name", &latest.name), ("count", &items.len().saturating_sub(1).to_string())],
|
||||
);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), label.as_str()));
|
||||
scenario::publish_target("viewport_status:summary", rect);
|
||||
scenario::publish_target(&target(latest, "summary"), rect);
|
||||
solidcraft_ui_registry::publish_target("viewport_status:summary", rect);
|
||||
solidcraft_ui_registry::publish_target(&target(latest, "summary"), rect);
|
||||
response
|
||||
},
|
||||
|ui| {
|
||||
@@ -414,13 +417,13 @@ pub(crate) fn viewport_status_summary(
|
||||
};
|
||||
ui.painter().circle_filled(icon.center(), (icon.width() - t.space_4) / 2.0, disc);
|
||||
crate::icons::paint_icon(ui.painter(), icon.shrink(t.space_4), item.kind.icon(), 1.0);
|
||||
scenario::publish_target(&target(item, "icon"), icon);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "icon"), icon);
|
||||
let name = ui.label(RichText::new(&item.name).semibold().size(t.font_label));
|
||||
scenario::publish_target(&target(item, "name"), name.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "name"), name.rect);
|
||||
let setting = RichText::new(&item.setting).monospace().size(t.font_small).color(t.text_dim);
|
||||
if item.editable {
|
||||
let response = super::button(ui, setting, Button::Ghost);
|
||||
scenario::publish_target(&target(item, "setting"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "setting"), response.rect);
|
||||
state.focus_ids.push(response.id);
|
||||
if response.clicked() {
|
||||
actions.push(StatusAction::Edit(item.id.clone()));
|
||||
@@ -428,7 +431,7 @@ pub(crate) fn viewport_status_summary(
|
||||
}
|
||||
} else {
|
||||
let response = ui.label(setting);
|
||||
scenario::publish_target(&target(item, "setting"), response.rect);
|
||||
solidcraft_ui_registry::publish_target(&target(item, "setting"), response.rect);
|
||||
}
|
||||
if item.has_key {
|
||||
let label = crate::i18n::format("ui.viewport_status_key", &[("name", &item.name)]);
|
||||
@@ -481,8 +484,8 @@ pub(crate) fn viewport_status_summary(
|
||||
}
|
||||
});
|
||||
});
|
||||
scenario::publish_rect("viewport_status:row", row.response.rect);
|
||||
scenario::publish_count("viewport_status:count", items.len() as f64);
|
||||
solidcraft_ui_registry::publish_rect("viewport_status:row", row.response.rect);
|
||||
solidcraft_ui_registry::publish_count("viewport_status:count", items.len() as f64);
|
||||
if let Some(id) = state.open_key.clone() {
|
||||
show_card(ctx, free, row.response.rect.left_bottom(), row.response.rect, &id, state, &mut key_contents);
|
||||
} else {
|
||||
@@ -519,7 +522,7 @@ fn show_card(
|
||||
card.show_frame(ui, frame, free, |ui| contents(ui, id));
|
||||
},
|
||||
);
|
||||
scenario::publish_target("viewport_status:card", result.response.rect);
|
||||
solidcraft_ui_registry::publish_target("viewport_status:card", result.response.rect);
|
||||
if state.opened_frame != Some(ctx.cumulative_frame_nr())
|
||||
&& !popup_was_open
|
||||
&& !egui::Popup::is_any_open(ctx)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//! Wheel input for the kit's scrolled strips. Camera input never reads the wheel here: it goes
|
||||
//! through the app's navigation handler.
|
||||
|
||||
use egui::Ui;
|
||||
|
||||
/// Take a predominantly horizontal wheel gesture for a hovered scroll viewport.
|
||||
/// Leave vertical input for its enclosing page, and leave horizontal input
|
||||
/// unconsumed when this viewport is already at the requested end.
|
||||
pub(crate) fn take_horizontal_wheel(ui: &Ui, offset: f32, max_offset: f32) -> f32 {
|
||||
ui.input_mut(|input| {
|
||||
let delta = input.smooth_scroll_delta;
|
||||
if delta.x == 0.0 || delta.x.abs() <= delta.y.abs() {
|
||||
return 0.0;
|
||||
}
|
||||
if (offset - delta.x).clamp(0.0, max_offset) != offset {
|
||||
input.smooth_scroll_delta.x = 0.0;
|
||||
}
|
||||
delta.x
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user