Feat/Sketch: associative pattern tools with independent directions and Flip/Center controls (#1601)
Thanks @Karl-VonRichter.
This commit is contained in:
1 parent
44968dbfe6
commit
371578a06f
36 files changed
+1394
-87
No files matched your search
+103
-22
@@ -819,7 +819,8 @@ impl Dialog {
|
||||
|
||||
/// Dialogs holding a table get the wide limit.
|
||||
pub fn is_wide(&self) -> bool {
|
||||
matches!(self.kind, Kind::Params { .. }) || matches!(&self.kind, Kind::Motion(k) if k.wide())
|
||||
matches!(self.kind, Kind::Params { .. } | Kind::SketchModify(crate::sketch_modify_ui::Mod::PatternRect { .. }))
|
||||
|| matches!(&self.kind, Kind::Motion(k) if k.wide())
|
||||
}
|
||||
|
||||
pub fn wants_picks(&self) -> bool {
|
||||
@@ -1609,6 +1610,10 @@ fn selection_label(s: &Session, item: &Sel) -> String {
|
||||
/// Selection input rows: label, "N selected" (or a hint) and a clear button; clicking a row
|
||||
/// makes it the active input.
|
||||
fn input_rows(d: &mut Dialog, ui: &mut egui::Ui, s: &Session) {
|
||||
input_rows_range(d, ui, s, 0..d.inputs.len());
|
||||
}
|
||||
|
||||
fn input_rows_range(d: &mut Dialog, ui: &mut egui::Ui, s: &Session, range: std::ops::Range<usize>) {
|
||||
// Edit Text shows its Path input among its own rows.
|
||||
if matches!(d.kind, Kind::Text(_)) {
|
||||
return;
|
||||
@@ -1616,7 +1621,7 @@ fn input_rows(d: &mut Dialog, ui: &mut egui::Ui, s: &Session) {
|
||||
let t = Tokens::get();
|
||||
let mut clear: Option<usize> = None;
|
||||
let mut activate: Option<usize> = None;
|
||||
for (i, inp) in d.inputs.iter().enumerate() {
|
||||
for (i, inp) in d.inputs.iter().enumerate().skip(range.start).take(range.end.saturating_sub(range.start)) {
|
||||
row_label(ui, inp.label);
|
||||
ui.horizontal(|ui| {
|
||||
let active = i == d.active;
|
||||
@@ -1685,6 +1690,87 @@ fn input_rows(d: &mut Dialog, ui: &mut egui::Ui, s: &Session) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each live reference sits with the independent values it drives.
|
||||
fn rectangular_pattern_rows(d: &mut Dialog, ui: &mut egui::Ui, session: &Session) -> bool {
|
||||
use crate::sketch_modify_ui::Mod;
|
||||
use solidcraft_engine::sketch::PatternAlignment;
|
||||
let mut enter = false;
|
||||
ui.vertical(|ui| {
|
||||
ui.label(tr("dialog.selection_guidance"));
|
||||
egui::Grid::new("pattern_source").spacing(vec2(10.0, 8.0)).show(ui, |ui| input_rows_range(d, ui, session, 0..1));
|
||||
ui.separator();
|
||||
ui.columns(2, |columns| {
|
||||
for (index, ui) in columns.iter_mut().enumerate() {
|
||||
ui.strong(tr(if index == 0 { "Direction 1" } else { "Direction 2" }));
|
||||
egui::Grid::new(("pattern_direction", index)).spacing(vec2(8.0, 8.0)).show(ui, |ui| {
|
||||
input_rows_range(d, ui, session, index + 1..index + 2);
|
||||
let Kind::SketchModify(Mod::PatternRect { count, spacing, count2, spacing2, alignment, alignment2, reversed, reversed2 }) =
|
||||
&mut d.kind
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let (count, spacing, alignment, reversed, count_label, spacing_label, alignment_label) = if index == 0 {
|
||||
(count, spacing, alignment, reversed, "Quantity", "Spacing", "Alignment")
|
||||
} else {
|
||||
(count2, spacing2, alignment2, reversed2, "Quantity 2", "Spacing 2", "Alignment 2")
|
||||
};
|
||||
row_label(ui, count_label);
|
||||
enter |= field(ui, count);
|
||||
ui.end_row();
|
||||
row_label(ui, spacing_label);
|
||||
enter |= field(ui, spacing);
|
||||
ui.end_row();
|
||||
row_label(ui, alignment_label);
|
||||
ui.horizontal(|ui| {
|
||||
pattern_toggle(ui, alignment_label, "Flip direction", "pattern_flip", reversed);
|
||||
let mut centered = *alignment == PatternAlignment::Center;
|
||||
pattern_toggle(ui, alignment_label, "Center", "pattern_center", &mut centered);
|
||||
*alignment = if centered { PatternAlignment::Center } else { PatternAlignment::Right };
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
});
|
||||
ui.small(tr("Counts include the original. Center balances copies; Flip chooses the extra side for even totals."));
|
||||
});
|
||||
ui.end_row();
|
||||
enter
|
||||
}
|
||||
|
||||
pub(crate) fn pattern_toggle(ui: &mut egui::Ui, row: &str, label: &str, icon: &str, selected: &mut bool) {
|
||||
let response = ui.add(egui::Button::new("").selected(*selected).min_size(vec2(28.0, 24.0)));
|
||||
let t = Tokens::get();
|
||||
crate::icons::paint(ui.painter(), response.rect.shrink(3.0), icon, t.icon, t.icon_fill, t.accent);
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Button, true, *selected, tr(label)));
|
||||
crate::scenario::publish_handle(&format!("dialog:{row}:{label}"), response.rect.center());
|
||||
if response.on_hover_text(tr(label)).clicked() {
|
||||
*selected = !*selected;
|
||||
}
|
||||
}
|
||||
|
||||
fn error_rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui: &mut egui::Ui, full_width: bool) {
|
||||
if matches!(d.kind, Kind::SketchModify(_))
|
||||
&& d.previews()
|
||||
&& let Err(error) = apply_commands(app, d)
|
||||
{
|
||||
d.refuse(app, error);
|
||||
}
|
||||
if let Some(error) = d.error.as_ref().or(app.preview.error.as_ref()).cloned() {
|
||||
if !full_width {
|
||||
row_label(ui, "");
|
||||
}
|
||||
ui.vertical(|ui| {
|
||||
ui.add(egui::Label::new(RichText::new(error.as_str()).color(Tokens::get().error)).wrap());
|
||||
let response = ui.button("Create Issue…");
|
||||
crate::scenario::publish_handle("dialog:create_issue", response.rect.center());
|
||||
if response.clicked() {
|
||||
crate::issue_report::begin(app, ctx, Some(d), Some(&error));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
|
||||
/// Width limits of a docked dialog: shrink-wrapped to its content within these.
|
||||
use crate::frame::{DIALOG_MAX_W, DIALOG_MIN_W, DIALOG_WIDE_MAX_W};
|
||||
/// Width of value fields and choice boxes in a dialog.
|
||||
@@ -1998,6 +2084,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let mut cancel = false;
|
||||
let mut enter = false;
|
||||
let wide = d.is_wide();
|
||||
let pattern_columns = matches!(d.kind, Kind::SketchModify(crate::sketch_modify_ui::Mod::PatternRect { .. }));
|
||||
let raw_title = title(&d.kind);
|
||||
let heading = if d.editing.is_some() { crate::i18n::format("dialog.edit_title", &[("title", tr(raw_title))]) } else { tr(raw_title).to_string() };
|
||||
let frame = crate::frame::dock_frame(ctx, side);
|
||||
@@ -2027,7 +2114,13 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
// Long texts (errors, notes) wrap instead of widening the dialog.
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
|
||||
let margin = horizontal_frame;
|
||||
let min_w = if wide { 480.0 } else { DIALOG_MIN_W } - margin;
|
||||
let min_w = if pattern_columns {
|
||||
540.0
|
||||
} else if wide {
|
||||
480.0
|
||||
} else {
|
||||
DIALOG_MIN_W
|
||||
} - margin;
|
||||
ui.set_min_width(min_w);
|
||||
ui.set_max_width(if wide { DIALOG_WIDE_MAX_W } else { DIALOG_MAX_W } - margin);
|
||||
// Rows that end at the right edge line up with last frame's content width.
|
||||
@@ -2081,7 +2174,12 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if let Kind::Motion(k @ crate::dialogs_motion::Mo::Configurations { .. }) = &mut d.kind {
|
||||
enter |= crate::dialogs_motion::rows(app, ui, k, &mut d.inputs);
|
||||
}
|
||||
egui::Grid::new("sc_dialog_grid").num_columns(2).spacing(vec2(10.0, 8.0)).show(ui, |ui| {
|
||||
egui::Grid::new("sc_dialog_grid").num_columns(if pattern_columns { 1 } else { 2 }).spacing(vec2(10.0, 8.0)).show(ui, |ui| {
|
||||
if pattern_columns {
|
||||
enter |= rectangular_pattern_rows(&mut d, ui, &app.session);
|
||||
error_rows(app, ctx, &mut d, ui, true);
|
||||
return;
|
||||
}
|
||||
if matches!(&d.kind, Kind::Motion(crate::dialogs_motion::Mo::Configurations { .. })) {
|
||||
return;
|
||||
}
|
||||
@@ -2798,24 +2896,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches!(d.kind, Kind::SketchModify(_))
|
||||
&& d.previews()
|
||||
&& let Err(error) = apply_commands(app, &d)
|
||||
{
|
||||
d.refuse(app, error);
|
||||
}
|
||||
if let Some(e) = d.error.as_ref().or(app.preview.error.as_ref()).cloned() {
|
||||
row_label(ui, "");
|
||||
ui.vertical(|ui| {
|
||||
ui.add(egui::Label::new(RichText::new(e.as_str()).color(t.error)).wrap());
|
||||
let response = ui.button("Create Issue…");
|
||||
crate::scenario::publish_handle("dialog:create_issue", response.rect.center());
|
||||
if response.clicked() {
|
||||
crate::issue_report::begin(app, ctx, Some(&d), Some(&e));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
error_rows(app, ctx, &mut d, ui, false);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
Reference in new issue
Block a user