Snap: view commands and guide preferences

This commit is contained in:
Cameron Gilroy
2026-10-06 09:05:59 -04:00
committed by Brandon Thomas
parent 09c8dc3f06
commit b0f9773205
5 changed files with 129 additions and 2 deletions
+27
View File
@@ -641,6 +641,18 @@ fn preferences(ui: &mut egui::Ui, d: &mut Dialog) {
text_field(ui, d, "pasteboard.v", 80.0);
ui.end_row();
});
ui.add_space(6.0);
check(ui, d, "snap.alignEdges", "Align to Object Edges");
check(ui, d, "snap.alignCenters", "Align to Object Centers");
check(ui, d, "snap.dimensions", "Smart Dimensions");
check(ui, d, "snap.spacing", "Smart Spacing");
ui.horizontal(|ui| {
ui.label("Snap to Zone");
let mut zone = d.n("snap.zone").filter(|z| z.is_finite()).unwrap_or(4.0);
if ui.add(egui::DragValue::new(&mut zone).speed(0.1)).changed() {
d.fields.insert("snap.zone".into(), json!(zone));
}
});
}
"display" => {
ui.label(egui::RichText::new("Options").font(semibold(12.0)));
@@ -1422,6 +1434,21 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
if let Some(v) = d.n("uiScale") {
app.run("window.uiScale", json!({"scale": v / 100.0}))?;
}
if d.fields.contains_key("snap.alignEdges") {
app.ui.align_edges = d.b("snap.alignEdges");
}
if d.fields.contains_key("snap.alignCenters") {
app.ui.align_centers = d.b("snap.alignCenters");
}
if d.fields.contains_key("snap.dimensions") {
app.ui.smart_dimensions = d.b("snap.dimensions");
}
if d.fields.contains_key("snap.spacing") {
app.ui.smart_spacing = d.b("snap.spacing");
}
if let Some(zone) = d.n("snap.zone").filter(|z| z.is_finite()) {
app.ui.snap_zone = if zone < 0.0 { 0.0 } else { zone };
}
if !d.fields.contains_key("horizontalUnits") {
return Ok(Value::Null);
}
+17
View File
@@ -101,6 +101,23 @@ const TABLE: &[(&str, [&str; 4])] = &[
("Screen Mode", ["Bildschirmmodus", "Mode d'affichage", "Modo de pantalla", "スクリーンモード"]),
("Grids & Guides", ["Raster und Hilfslinien", "Grilles et repères", "Cuadrículas y guías", "グリッドとガイド"]),
("Show Rulers", ["Lineale einblenden", "Afficher les règles", "Mostrar reglas", "定規を表示"]),
("Snap to Guides", ["An Hilfslinien ausrichten", "Coller aux repères", "Ajustar a las guías", "ガイドにスナップ"]),
(
"Snap to Document Grid",
["Am Dokumentraster ausrichten", "Coller à la grille du document", "Ajustar a la cuadrícula del documento", "ドキュメントグリッドにスナップ"],
),
("Smart Guides", ["Smarte Hilfslinien", "Repères intelligents", "Guías inteligentes", "スマートガイド"]),
(
"Align to Object Edges",
["An Objektkanten ausrichten", "Aligner sur les bords des objets", "Alinear a los bordes del objeto", "オブジェクトの端に揃える"],
),
(
"Align to Object Centers",
["An Objektmitten ausrichten", "Aligner sur les centres des objets", "Alinear a los centros del objeto", "オブジェクトの中心に揃える"],
),
("Smart Dimensions", ["Smarte Maße", "Mesures intelligentes", "Dimensiones inteligentes", "スマートサイズ"]),
("Smart Spacing", ["Smarte Abstände", "Espacements intelligents", "Espaciado inteligente", "スマート間隔"]),
("Snap to Zone", ["Fangzone", "Zone de magnétisme", "Zona de ajuste", "スナップゾーン"]),
// Window and panels.
("Properties", ["Eigenschaften", "Propriétés", "Propiedades", "プロパティ"]),
("Layers", ["Ebenen", "Calques", "Capas", "レイヤー"]),
+65 -1
View File
@@ -86,6 +86,16 @@ pub struct SavedWorkspace {
pub floating: Vec<(String, [f32; 2])>,
}
/// A missing on-by-default bool stays on. `bool::default` is false.
fn default_true() -> bool {
true
}
/// A missing snap zone stays at the factory width, in screen pixels.
fn default_zone() -> f64 {
4.0
}
/// Persisted UI state.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
@@ -162,6 +172,19 @@ pub struct UiState {
pub text_style_tab: u8,
pub guides_locked: bool,
pub smart_guides: bool,
#[serde(default = "default_true")]
pub snap_to_guides: bool,
pub snap_to_document_grid: bool,
#[serde(default = "default_true")]
pub align_edges: bool,
#[serde(default = "default_true")]
pub align_centers: bool,
#[serde(default = "default_true")]
pub smart_dimensions: bool,
#[serde(default = "default_true")]
pub smart_spacing: bool,
#[serde(default = "default_zone")]
pub snap_zone: f64,
/// Window > Contextual Task Bar.
pub task_bar: bool,
/// Help › About DesignCraft is open.
@@ -228,6 +251,13 @@ impl Default for UiState {
text_style_tab: 0,
guides_locked: false,
smart_guides: true,
snap_to_guides: true,
snap_to_document_grid: false,
align_edges: true,
align_centers: true,
smart_dimensions: true,
smart_spacing: true,
snap_zone: 4.0,
task_bar: true,
about: false,
pending_urls: Vec::new(),
@@ -239,6 +269,23 @@ impl Default for UiState {
}
}
impl UiState {
/// Copies the live view switches into the snap engine's view.
pub fn snap_view(&self) -> SnapView {
SnapView {
snap_to_guides: self.snap_to_guides,
snap_to_document_grid: self.snap_to_document_grid,
show_guides: self.guides,
smart_guides: self.smart_guides,
align_edges: self.align_edges,
align_centers: self.align_centers,
smart_dimensions: self.smart_dimensions,
smart_spacing: self.smart_spacing,
zone_px: self.snap_zone,
}
}
}
/// Per-document view: zoom (screen points per document point) and the canvas point at the
/// top-left of the canvas area.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
@@ -429,7 +476,7 @@ impl DesignApp {
self.pane = pane;
}
pub fn view_info(&self) -> ViewInfo {
ViewInfo { zoom: self.view().map(|v| v.zoom).unwrap_or(1.0), snap: SnapView::FACTORY, unit: self.ui.units }
ViewInfo { zoom: self.view().map(|v| v.zoom).unwrap_or(1.0), snap: self.ui.snap_view(), unit: self.ui.units }
}
pub fn status(&mut self, s: impl Into<String>) {
@@ -806,3 +853,20 @@ pub fn now_ms() -> f64 {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snap_view_uses_the_saved_switches() {
let mut ui = UiState::default();
assert!(ui.snap_view().snap_to_guides);
assert!(!ui.snap_view().snap_to_document_grid);
assert_eq!(ui.snap_view().zone_px, 4.0);
ui.snap_to_guides = false;
ui.snap_zone = 0.0;
assert!(!ui.snap_view().snap_to_guides);
assert_eq!(ui.snap_view().zone_px, 0.0);
}
}
+17
View File
@@ -77,6 +77,9 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
("view.frameEdges", "Show/Hide Frame Edges", Some("Cmd+H"), "{}"),
("view.rulers", "Show/Hide Rulers", Some("Cmd+R"), "{}"),
("view.guides", "Show/Hide Guides", Some("Cmd+;"), "{}"),
("view.snapToGuides", "Snap to Guides", Some("Cmd+Shift+;"), "{}"),
("view.snapToDocumentGrid", "Snap to Document Grid", None, "{}"),
("view.smartGuides", "Smart Guides", None, "{}"),
("view.baselineGrid", "Show/Hide Baseline Grid", Some("Cmd+Alt+'"), "{}"),
("view.textThreads", "Show/Hide Text Threads", Some("Cmd+Alt+Y"), "{}"),
("view.hiddenCharacters", "Show/Hide Hidden Characters", Some("Cmd+Alt+I"), "{}"),
@@ -584,6 +587,9 @@ pub const MENUS: &[(&str, &[&str])] = &[
"-",
">Grids & Guides",
"ui:view.guides",
"ui:view.snapToGuides",
"ui:view.snapToDocumentGrid",
"ui:view.smartGuides",
"ui:view.baselineGrid",
"-",
"ui:app.deleteAllGuides",
@@ -959,6 +965,11 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
f["pasteboard.h"] = m(&doc["pasteboard"][0]);
f["pasteboard.v"] = m(&doc["pasteboard"][1]);
}
f["snap.alignEdges"] = json!(app.ui.align_edges);
f["snap.alignCenters"] = json!(app.ui.align_centers);
f["snap.dimensions"] = json!(app.ui.smart_dimensions);
f["snap.spacing"] = json!(app.ui.smart_spacing);
f["snap.zone"] = json!(app.ui.snap_zone);
f["displayQuality"] = json!(match app.ui.display_quality {
designcraft_render::DisplayQuality::Fast => "fast",
designcraft_render::DisplayQuality::Typical => "typical",
@@ -1042,6 +1053,9 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
}
"view.rulers" => flag(&mut app.ui.rulers),
"view.guides" => flag(&mut app.ui.guides),
"view.snapToGuides" => flag(&mut app.ui.snap_to_guides),
"view.snapToDocumentGrid" => flag(&mut app.ui.snap_to_document_grid),
"view.smartGuides" => flag(&mut app.ui.smart_guides),
"view.baselineGrid" => flag(&mut app.ui.baseline_grid),
"view.textThreads" => flag(&mut app.ui.text_threads),
"view.hiddenCharacters" => flag(&mut app.ui.hidden_characters),
@@ -1606,6 +1620,9 @@ pub fn checked(app: &DesignApp, id: &str, params: &Value) -> Option<bool> {
"view.typicalDisplay" => app.ui.display_quality == designcraft_render::DisplayQuality::Typical,
"view.highQualityDisplay" => app.ui.display_quality == designcraft_render::DisplayQuality::High,
"view.guides" => app.ui.guides,
"view.snapToGuides" => app.ui.snap_to_guides,
"view.snapToDocumentGrid" => app.ui.snap_to_document_grid,
"view.smartGuides" => app.ui.smart_guides,
"view.baselineGrid" => app.ui.baseline_grid,
"view.textThreads" => app.ui.text_threads,
"view.hiddenCharacters" => app.ui.hidden_characters,
+3 -1
View File
@@ -358,8 +358,10 @@ fn document_sections(app: &mut DesignApp, ui: &mut Ui) {
if guides != app.ui.guides {
let _ = app.run("view.guides", json!({}));
}
if smart != app.ui.smart_guides {
let _ = app.run("view.smartGuides", json!({}));
}
app.ui.guides_locked = locked;
app.ui.smart_guides = smart;
rule(ui);
section(ui, "Quick Actions", true);
let (a, b) = button_pair(ui, "Import File", "Insert Page");