fix(web): render UI icons as SVG instead of font glyphs
UI chrome (dropdown carets, undo/redo, checkmarks, close/menu/osnap, layer/file-menu icons) and ribbon tool icons were drawn as Unicode glyphs. Desktop fills missing glyphs via system-font fallback, but the web build bundles only Fira Sans with no fallback, so they rendered as empty tofu boxes (issue #116). Add a shared src/ui/icons.rs (SVG-backed, tintable) plus bundled assets, and switch every glyph site and IconKind::Glyph tool def to SVG. Co-Authored-By: Claude Opus 4.8 <[email protected]>
No files matched your search
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M4 7 H16 M4 12 H12 M4 17 H10" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17 14 V22 M13 18 H21" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 360 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M12 3 L20 7 L12 11 L4 7 Z" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M4 7 L4 16 L12 20 L12 11" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 7 L20 16 L12 20" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M12 11 L12 20" stroke="#4cc9f0" stroke-width="1.4" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M12 3 L21 7.5 L12 12 L3 7.5 Z" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M3 12 L12 16.5 L21 12" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M3 16.5 L12 21 L21 16.5" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 494 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M4 7 H20" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9 7 V5 a1 1 0 0 1 1-1 H14 a1 1 0 0 1 1 1 V7" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6 7 L7 20 a1 1 0 0 0 1 1 H16 a1 1 0 0 0 1-1 L18 7" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 11 V17 M14 11 V17" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 654 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<rect x="3.5" y="6" width="9" height="11" rx="1" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<rect x="8" y="8.5" width="9" height="11" rx="1" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<rect x="12.5" y="4.5" width="8" height="11" rx="1" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 538 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M12 3 L5 18 M12 3 L19 18" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<ellipse cx="12" cy="18" rx="7" ry="2.6" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 365 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<ellipse cx="12" cy="5.5" rx="6.5" ry="2.6" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.5 5.5 L5.5 18 M18.5 5.5 L18.5 18" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.5 18 A6.5 2.6 0 0 0 18.5 18" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M3 7 H9 L11 9 H21 V19 H3 Z" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M12 11 V16 M9.5 13.5 L12 16 L14.5 13.5" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<circle cx="9" cy="12" r="6.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15" cy="12" r="6.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<ellipse cx="12" cy="12" rx="2.1" ry="4.3" fill="#4cc9f0" stroke="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 428 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<circle cx="12" cy="12" r="3.2" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M12 2.5 V5 M12 19 V21.5 M2.5 12 H5 M19 12 H21.5 M5 5 L6.8 6.8 M17.2 17.2 L19 19 M19 5 L17.2 6.8 M6.8 17.2 L5 19" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M6 3 H14 L19 8 V21 H6 Z M14 3 V8 H19" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M8.5 12 H15 M8.5 15.5 H15" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 372 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M7 9 V3 H17 V9" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M7 17 H4 V10 a1 1 0 0 1 1-1 H19 a1 1 0 0 1 1 1 V17 H17" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<rect x="7" y="14" width="10" height="7" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M5 6 H19 M5 12 H19 M5 18 H14" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5 6 V8 M9 6 V8 M13 6 V8 M17 6 V8" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 372 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<circle cx="12" cy="12" r="8.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<ellipse cx="12" cy="12" rx="8.2" ry="3" stroke="#4cc9f0" stroke-width="1.3" fill="none"/>
|
||||
<ellipse cx="12" cy="12" rx="3" ry="8.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<circle cx="9" cy="12" r="6.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.5" cy="12" r="6.2" stroke="#4cc9f0" stroke-width="1.4" fill="none" stroke-dasharray="3 2.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 332 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M5 9 H19 M16 6 L19 9 L16 12" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M19 15 H5 M8 12 L5 15 L8 18" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 365 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<ellipse cx="12" cy="12" rx="9" ry="5.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<ellipse cx="12" cy="12" rx="3.6" ry="1.9" stroke="#4cc9f0" stroke-width="1.4" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 326 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12 H20 M14 6 L20 12 L14 18"/></svg>
|
||||
|
After Width: | Height: | Size: 201 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" stroke="none"><path d="M13 2 L4 13 H11 L10 22 L20 10 H12 Z"/></svg>
|
||||
|
After Width: | Height: | Size: 143 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12.5 L9.5 18 L20 6"/></svg>
|
||||
|
After Width: | Height: | Size: 195 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6 L18 18 M18 6 L6 18"/></svg>
|
||||
|
After Width: | Height: | Size: 197 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3 H14 L19 8 V21 H6 Z M14 3 V8 H19 M8.5 12 H15 M8.5 15.5 H15 M8.5 8.5 H11"/></svg>
|
||||
|
After Width: | Height: | Size: 249 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3 H14 L19 8 V21 H6 Z M14 3 V8 H19"/></svg>
|
||||
|
After Width: | Height: | Size: 210 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" stroke="none"><circle cx="12" cy="12" r="4.5"/></svg>
|
||||
|
After Width: | Height: | Size: 129 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6 H9 L11 8 H21 V19 H3 Z"/></svg>
|
||||
|
After Width: | Height: | Size: 200 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M12 2 V5 M12 19 V22 M2 12 H5 M19 12 H22 M4.9 4.9 L7 7 M17 17 L19.1 19.1 M19.1 4.9 L17 7 M7 17 L4.9 19.1"/></svg>
|
||||
|
After Width: | Height: | Size: 310 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" stroke="none"><path d="M12 21 C12 21 3 14.5 3 8.8 A4.8 4.8 0 0 1 12 6.5 A4.8 4.8 0 0 1 21 8.8 C21 14.5 12 21 12 21 Z"/></svg>
|
||||
|
After Width: | Height: | Size: 201 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9 9.5 a3 3 0 0 1 5.5 1.6 c0 2-2.5 2.4-2.5 4"/><path d="M12 17.5 L12 17.5"/></svg>
|
||||
|
After Width: | Height: | Size: 278 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round"><path d="M4 7 H20 M4 12 H20 M4 17 H20"/></svg>
|
||||
|
After Width: | Height: | Size: 176 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 V21 M3 12 H21 M12 3 L9 6 M12 3 L15 6 M12 21 L9 18 M12 21 L15 18 M3 12 L6 9 M3 12 L6 15 M21 12 L18 9 M21 12 L18 15"/></svg>
|
||||
|
After Width: | Height: | Size: 293 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2.2" stroke-linecap="round"><path d="M12 5 L12 19 M5 12 L19 12"/></svg>
|
||||
|
After Width: | Height: | Size: 175 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M7 9 V3 H17 V9 M7 17 H4 V10 a1 1 0 0 1 1-1 H19 a1 1 0 0 1 1 1 V17 H17 M7 14 H17 V21 H7 Z M16 12 H17"/></svg>
|
||||
|
After Width: | Height: | Size: 273 B |
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="2.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="redo.svg"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="17.948527"
|
||||
inkscape:cx="22.007378"
|
||||
inkscape:cy="9.4436721"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="939"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="m 16,3 5,5 -5,5"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M 21,8 H 10 a 6,6 0 0 0 0,12 h 2"
|
||||
id="path2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 H17 L20 7 V20 H4 Z M8 4 V9 H15 V4 M7 20 V14 H17 V20"/></svg>
|
||||
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="1.5"/><path d="M4 12 H20" stroke-width="1.8"/></svg>
|
||||
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="1.5"/><path d="M12 5 V19" stroke-width="1.8"/></svg>
|
||||
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7 H20 M9 7 V5 a1 1 0 0 1 1-1 H14 a1 1 0 0 1 1 1 V7 M6 7 L7 20 a1 1 0 0 0 1 1 H16 a1 1 0 0 0 1-1 L18 7 M10 11 V17 M14 11 V17"/></svg>
|
||||
|
After Width: | Height: | Size: 300 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path d="M2 4.5 L10 4.5 L6 9 Z" fill="#000000"/></svg>
|
||||
|
After Width: | Height: | Size: 115 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" fill="#000000"><path d="M7.5 2 L7.5 10 L3 6 Z"/></svg>
|
||||
|
After Width: | Height: | Size: 115 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path d="M4.5 2 L4.5 10 L9 6 Z" fill="#000000"/></svg>
|
||||
|
After Width: | Height: | Size: 115 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path d="M2 7.5 L10 7.5 L6 3 Z" fill="#000000"/></svg>
|
||||
|
After Width: | Height: | Size: 115 B |
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="2.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="undo.svg"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="17.852059"
|
||||
inkscape:cx="8.0102804"
|
||||
inkscape:cy="15.376377"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="939"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M 8,3 3,8 8,13"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 3,8 h 11 a 6,6 0 0 1 0,12 h -2"
|
||||
id="path2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4 L12 20 M5 11 L12 4 L19 11"/></svg>
|
||||
|
After Width: | Height: | Size: 205 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<circle cx="9" cy="12" r="6.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15" cy="12" r="6.2" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 352 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<rect x="3" y="5" width="18" height="14" rx="1" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<rect x="6.5" y="8" width="11" height="8" rx="0.5" stroke="#4cc9f0" stroke-width="1.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||
<path d="M4 18 L4 8 L16 18 Z" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M4 8 L8 6 L20 16 L16 18" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 16 L20 16" stroke="#e0e0e0" stroke-width="1.7" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M4 18 L8 6" stroke="#4cc9f0" stroke-width="1.3" fill="none" opacity="0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
@@ -367,13 +367,17 @@ impl OpenCADStudio {
|
||||
if !is_paper && !tab.is_start {
|
||||
// Render-mode picker + viewport-split buttons (horizontal /
|
||||
// vertical divider of the active Model tile).
|
||||
let split_btn = |glyph: &'static str, horizontal: bool| {
|
||||
button(text(glyph).size(13).color(Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
}))
|
||||
let split_btn = |bytes: &'static [u8], horizontal: bool| {
|
||||
button(crate::ui::icons::tinted(
|
||||
bytes,
|
||||
14.0,
|
||||
Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
},
|
||||
))
|
||||
.on_press(Message::SplitModelViewport(horizontal))
|
||||
.padding([4, 8])
|
||||
.style(|_: &Theme, status| iced::widget::button::Style {
|
||||
@@ -410,7 +414,12 @@ impl OpenCADStudio {
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
let bar = row![info, split_btn("▤", true), split_btn("▥", false)].spacing(4);
|
||||
let bar = row![
|
||||
info,
|
||||
split_btn(crate::ui::icons::SPLIT_H, true),
|
||||
split_btn(crate::ui::icons::SPLIT_V, false)
|
||||
]
|
||||
.spacing(4);
|
||||
// Position the bar at the active model tile's top-left corner so
|
||||
// it follows the active panel in a tiled layout (full canvas when
|
||||
// a single tile fills the window). Leading Spaces offset it.
|
||||
@@ -636,9 +645,20 @@ impl OpenCADStudio {
|
||||
let mut col = column![].spacing(0).width(iced::Length::Fixed(row_w));
|
||||
for (idx, name) in popup.items.iter().enumerate() {
|
||||
let is_cur = popup.current == Some(idx);
|
||||
let label = format!("{} {}", if is_cur { "✓" } else { " " }, name);
|
||||
let btn = button(text(label).size(12).color(Color::WHITE))
|
||||
.on_press(Message::VisibilityPick(idx))
|
||||
let mark: Element<'_, Message> = if is_cur {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, Color::WHITE)
|
||||
} else {
|
||||
Space::new().width(11).into()
|
||||
};
|
||||
let btn = button(
|
||||
row![
|
||||
container(mark).width(16),
|
||||
text(name).size(12).color(Color::WHITE),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::VisibilityPick(idx))
|
||||
.padding([3, 10])
|
||||
.width(Fill)
|
||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
||||
@@ -1851,13 +1871,28 @@ pub(super) fn doc_tab_bar<'a>(tabs: &'a [DocumentTab], active_tab: usize) -> Ele
|
||||
for (idx, tab) in tabs.iter().enumerate() {
|
||||
let is_active = idx == active_tab;
|
||||
let name = crate::ui::text_util::elide(&tab.tab_display_name(), 24);
|
||||
let label = if tab.dirty {
|
||||
format!("● {}", name)
|
||||
let title_inner: Element<'_, Message> = if tab.dirty {
|
||||
row![
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::DOT,
|
||||
7.0,
|
||||
Color {
|
||||
r: 0.90,
|
||||
g: 0.75,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
},
|
||||
),
|
||||
text(name).size(12),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
} else {
|
||||
name
|
||||
text(name).size(12).into()
|
||||
};
|
||||
|
||||
let title_btn = button(text(label).size(12))
|
||||
let title_btn = button(title_inner)
|
||||
.on_press(Message::TabSwitch(idx))
|
||||
.padding([5, 12])
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
@@ -1888,12 +1923,16 @@ pub(super) fn doc_tab_bar<'a>(tabs: &'a [DocumentTab], active_tab: usize) -> Ele
|
||||
let row_inner: Row<'_, Message> = if tab.is_start {
|
||||
row![title_btn].spacing(0).align_y(iced::Center)
|
||||
} else {
|
||||
let close_btn = button(text("×").size(11).color(Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
}))
|
||||
let close_btn = button(crate::ui::icons::tinted(
|
||||
crate::ui::icons::CLOSE,
|
||||
10.0,
|
||||
Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
},
|
||||
))
|
||||
.on_press(Message::TabClose(idx))
|
||||
.padding([3, 5])
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
@@ -2825,12 +2864,35 @@ fn viewport_context_menu_overlay(
|
||||
Message::Command("COPY".to_string()),
|
||||
));
|
||||
items.push(sep());
|
||||
let arrow = if draworder_open {
|
||||
"Draw Order \u{25be}"
|
||||
let do_caret = if draworder_open {
|
||||
crate::ui::icons::arrow_down(9.0, TEXT_COL)
|
||||
} else {
|
||||
"Draw Order \u{25b8}"
|
||||
crate::ui::icons::arrow_right(9.0, TEXT_COL)
|
||||
};
|
||||
items.push(item(arrow.to_string(), Message::DrawOrderSubmenuToggle));
|
||||
items.push(
|
||||
button(
|
||||
row![
|
||||
text("Draw Order").size(12).color(TEXT_COL),
|
||||
iced::widget::Space::new().width(Fill),
|
||||
do_caret,
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::DrawOrderSubmenuToggle)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ITEM_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.padding([4, 12])
|
||||
.width(Fill)
|
||||
.into(),
|
||||
);
|
||||
if draworder_open {
|
||||
items.push(subitem(
|
||||
"Bring to Front".to_string(),
|
||||
@@ -3353,7 +3415,7 @@ fn save_as_dialog_window<'a>(
|
||||
let path_bar = row![
|
||||
{
|
||||
let up_msg = up_path.map(Message::SaveDialogNavigate);
|
||||
let b = button(text("↑").size(14).color(TEXT))
|
||||
let b = button(crate::ui::icons::tinted(crate::ui::icons::UP, 14.0, TEXT))
|
||||
.style(|_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(
|
||||
if matches!(st, button::Status::Hovered | button::Status::Pressed) {
|
||||
@@ -3398,14 +3460,18 @@ fn save_as_dialog_window<'a>(
|
||||
let rows: Vec<Element<'_, Message>> = entries
|
||||
.iter()
|
||||
.map(|(name, is_dir, path)| {
|
||||
let icon = if *is_dir { "📁" } else { "📄" };
|
||||
let icon_bytes = if *is_dir {
|
||||
crate::ui::icons::FOLDER
|
||||
} else {
|
||||
crate::ui::icons::DOC
|
||||
};
|
||||
let color = if *is_dir { DIR_COL } else { FILE_COL };
|
||||
let p = path.clone();
|
||||
let d = *is_dir;
|
||||
mouse_area(
|
||||
container(
|
||||
row![
|
||||
text(icon).size(13),
|
||||
crate::ui::icons::tinted(icon_bytes, 13.0, color),
|
||||
Space::new().width(6),
|
||||
text(crate::ui::text_util::elide(name.as_str(), 48))
|
||||
.size(13)
|
||||
@@ -3792,9 +3858,10 @@ pub(super) fn start_page_view<'a>() -> Element<'a, Message> {
|
||||
let donate_btn = {
|
||||
button(
|
||||
row![
|
||||
text("♥ ").size(15).color(Color::WHITE),
|
||||
crate::ui::icons::tinted(crate::ui::icons::HEART, 14.0, Color::WHITE),
|
||||
text("Donate").size(14).color(Color::WHITE),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::RibbonToolClick {
|
||||
@@ -4097,7 +4164,7 @@ pub(super) fn recent_files_panel<'a>(recents: &'a [std::path::PathBuf]) -> Eleme
|
||||
});
|
||||
|
||||
let path_for_remove = path.clone();
|
||||
let remove_btn = button(text("×").size(12).color(MUTED))
|
||||
let remove_btn = button(crate::ui::icons::tinted(crate::ui::icons::CLOSE, 11.0, MUTED))
|
||||
.on_press(Message::RecentRemove(path_for_remove))
|
||||
.padding([4, 8])
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
|
||||
@@ -193,25 +193,33 @@ impl CadModule for AnnotateModule {
|
||||
RibbonItem::Tool(crate::modules::ToolDef {
|
||||
id: "ANNOSCALE",
|
||||
label: "Scale List",
|
||||
icon: crate::modules::IconKind::Glyph("⬡"),
|
||||
icon: crate::modules::IconKind::Svg(include_bytes!(
|
||||
"../../../assets/icons/scale_list.svg"
|
||||
)),
|
||||
event: crate::modules::ModuleEvent::Command("ANNOSCALE".to_string()),
|
||||
}),
|
||||
RibbonItem::Tool(crate::modules::ToolDef {
|
||||
id: "OBJECTSCALE",
|
||||
label: "Add Scale",
|
||||
icon: crate::modules::IconKind::Glyph("⊕"),
|
||||
icon: crate::modules::IconKind::Svg(include_bytes!(
|
||||
"../../../assets/icons/add_scale.svg"
|
||||
)),
|
||||
event: crate::modules::ModuleEvent::Command("OBJECTSCALE".to_string()),
|
||||
}),
|
||||
RibbonItem::Tool(crate::modules::ToolDef {
|
||||
id: "SCALELISTEDIT",
|
||||
label: "Scale Edit",
|
||||
icon: crate::modules::IconKind::Glyph("≡"),
|
||||
icon: crate::modules::IconKind::Svg(include_bytes!(
|
||||
"../../../assets/icons/scale_list.svg"
|
||||
)),
|
||||
event: crate::modules::ModuleEvent::Command("SCALELISTEDIT".to_string()),
|
||||
}),
|
||||
RibbonItem::Tool(crate::modules::ToolDef {
|
||||
id: "SYNCPVIEWPORTS",
|
||||
label: "Sync Scales",
|
||||
icon: crate::modules::IconKind::Glyph("⇄"),
|
||||
icon: crate::modules::IconKind::Svg(include_bytes!(
|
||||
"../../../assets/icons/sync.svg"
|
||||
)),
|
||||
event: crate::modules::ModuleEvent::Command("SYNCPVIEWPORTS".to_string()),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "COLOR",
|
||||
label: "Color",
|
||||
icon: IconKind::Glyph("🎨"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../../assets/icons/color_palette.svg")),
|
||||
event: ModuleEvent::Command("COLOR".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "BYLAYER",
|
||||
label: "ByLayer",
|
||||
icon: IconKind::Glyph("⊟"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../../assets/icons/bylayer.svg")),
|
||||
event: ModuleEvent::Command("BYLAYER".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "BOX",
|
||||
label: "Box",
|
||||
icon: IconKind::Glyph("⬜"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/box3d.svg")),
|
||||
event: ModuleEvent::Command("BOX".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "CLEAR",
|
||||
label: "Clear",
|
||||
icon: IconKind::Glyph("🗑"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/clear_tool.svg")),
|
||||
event: ModuleEvent::ClearModels,
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "CYLINDER",
|
||||
label: "Cylinder",
|
||||
icon: IconKind::Glyph("⬡"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/cylinder3d.svg")),
|
||||
event: ModuleEvent::Command("CYLINDER".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "OPEN",
|
||||
label: "Open",
|
||||
icon: IconKind::Glyph("📂"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/import_obj.svg")),
|
||||
event: ModuleEvent::OpenFileDialog,
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "SPHERE",
|
||||
label: "Sphere",
|
||||
icon: IconKind::Glyph("⬤"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/sphere3d.svg")),
|
||||
event: ModuleEvent::Command("SPHERE".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,13 @@ pub fn paper_space_tools() -> Vec<ToolDef> {
|
||||
ToolDef {
|
||||
id: "PAGESETUP",
|
||||
label: "Page Setup",
|
||||
icon: IconKind::Glyph("📋"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/pagesetup.svg")),
|
||||
event: ModuleEvent::Command("PAGESETUP".to_string()),
|
||||
},
|
||||
ToolDef {
|
||||
id: "PLOT",
|
||||
label: "Export PDF",
|
||||
icon: IconKind::Glyph("🖨"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/plot.svg")),
|
||||
event: ModuleEvent::Command("PLOT".to_string()),
|
||||
},
|
||||
]
|
||||
@@ -48,14 +48,14 @@ impl CadModule for LayoutModule {
|
||||
ToolDef {
|
||||
id: "PAGESETUP",
|
||||
label: "Page Setup",
|
||||
icon: IconKind::Glyph("📋"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/pagesetup.svg")),
|
||||
event: ModuleEvent::Command("PAGESETUP".to_string()),
|
||||
}
|
||||
.into(),
|
||||
ToolDef {
|
||||
id: "PLOT",
|
||||
label: "Export PDF",
|
||||
icon: IconKind::Glyph("🖨"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/plot.svg")),
|
||||
event: ModuleEvent::Command("PLOT".to_string()),
|
||||
}
|
||||
.into(),
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "MVIEW",
|
||||
label: "Viewport",
|
||||
icon: IconKind::Glyph("⬜"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/viewport.svg")),
|
||||
event: ModuleEvent::Command("MVIEW".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "MVIEW",
|
||||
label: "Viewport",
|
||||
icon: IconKind::Glyph("⬜"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/viewport.svg")),
|
||||
event: ModuleEvent::Command("MVIEW".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "OPTIONS",
|
||||
label: "Options",
|
||||
icon: IconKind::Glyph("⚙"),
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/options_tool.svg")),
|
||||
event: ModuleEvent::Command("OPTIONS".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,22 @@ use crate::modules::{CadModule, IconKind, ModuleEvent, RibbonGroup, RibbonItem,
|
||||
|
||||
pub struct ModelModule;
|
||||
|
||||
const BOX_ICON: &[u8] = include_bytes!("../../../assets/icons/box3d.svg");
|
||||
const CYLINDER_ICON: &[u8] = include_bytes!("../../../assets/icons/cylinder3d.svg");
|
||||
const CONE_ICON: &[u8] = include_bytes!("../../../assets/icons/cone3d.svg");
|
||||
const SPHERE_ICON: &[u8] = include_bytes!("../../../assets/icons/sphere3d.svg");
|
||||
const WEDGE_ICON: &[u8] = include_bytes!("../../../assets/icons/wedge3d.svg");
|
||||
const TORUS_ICON: &[u8] = include_bytes!("../../../assets/icons/torus3d.svg");
|
||||
const UNION_ICON: &[u8] = include_bytes!("../../../assets/icons/union.svg");
|
||||
const SUBTRACT_ICON: &[u8] = include_bytes!("../../../assets/icons/subtract.svg");
|
||||
const INTERSECT_ICON: &[u8] = include_bytes!("../../../assets/icons/intersect.svg");
|
||||
|
||||
/// Helper to declare a ribbon tool that fires a named command.
|
||||
fn tool(id: &'static str, label: &'static str, glyph: &'static str) -> ToolDef {
|
||||
fn tool(id: &'static str, label: &'static str, icon: &'static [u8]) -> ToolDef {
|
||||
ToolDef {
|
||||
id,
|
||||
label,
|
||||
icon: IconKind::Glyph(glyph),
|
||||
icon: IconKind::Svg(icon),
|
||||
event: ModuleEvent::Command(id.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -35,16 +45,16 @@ impl CadModule for ModelModule {
|
||||
RibbonGroup {
|
||||
title: "Model",
|
||||
tools: vec![
|
||||
RibbonItem::LargeTool(tool("BOX", "Box", "▦")),
|
||||
RibbonItem::LargeTool(tool("CYLINDER", "Cylinder", "⬭")),
|
||||
RibbonItem::LargeTool(tool("CONE", "Cone", "△")),
|
||||
RibbonItem::LargeTool(tool("SPHERE", "Sphere", "◯")),
|
||||
RibbonItem::LargeTool(tool("BOX", "Box", BOX_ICON)),
|
||||
RibbonItem::LargeTool(tool("CYLINDER", "Cylinder", CYLINDER_ICON)),
|
||||
RibbonItem::LargeTool(tool("CONE", "Cone", CONE_ICON)),
|
||||
RibbonItem::LargeTool(tool("SPHERE", "Sphere", SPHERE_ICON)),
|
||||
RibbonItem::Dropdown {
|
||||
id: "MODEL_MORE",
|
||||
icon: IconKind::Glyph("◰"),
|
||||
icon: IconKind::Svg(WEDGE_ICON),
|
||||
items: vec![
|
||||
("WEDGE", "Wedge", IconKind::Glyph("◣")),
|
||||
("TORUS", "Torus", IconKind::Glyph("◎")),
|
||||
("WEDGE", "Wedge", IconKind::Svg(WEDGE_ICON)),
|
||||
("TORUS", "Torus", IconKind::Svg(TORUS_ICON)),
|
||||
],
|
||||
default: "WEDGE",
|
||||
},
|
||||
@@ -53,9 +63,9 @@ impl CadModule for ModelModule {
|
||||
RibbonGroup {
|
||||
title: "Design",
|
||||
tools: vec![
|
||||
RibbonItem::LargeTool(tool("UNION", "Union", "⊕")),
|
||||
RibbonItem::LargeTool(tool("SUBTRACT", "Subtract", "⊖")),
|
||||
RibbonItem::LargeTool(tool("INTERSECT", "Intersect", "⊗")),
|
||||
RibbonItem::LargeTool(tool("UNION", "Union", UNION_ICON)),
|
||||
RibbonItem::LargeTool(tool("SUBTRACT", "Subtract", SUBTRACT_ICON)),
|
||||
RibbonItem::LargeTool(tool("INTERSECT", "Intersect", INTERSECT_ICON)),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::app::Message;
|
||||
// ── Menu item definitions ─────────────────────────────────────────────────
|
||||
|
||||
struct MenuItem {
|
||||
icon: &'static str,
|
||||
icon: &'static [u8],
|
||||
label: &'static str,
|
||||
desc: &'static str,
|
||||
command: &'static str,
|
||||
@@ -26,43 +26,43 @@ struct MenuItem {
|
||||
|
||||
const MENU_ITEMS: &[MenuItem] = &[
|
||||
MenuItem {
|
||||
icon: "◻",
|
||||
icon: crate::ui::icons::DOC_NEW,
|
||||
label: "New",
|
||||
desc: "Create a new drawing",
|
||||
command: "NEW",
|
||||
},
|
||||
MenuItem {
|
||||
icon: "📂",
|
||||
icon: crate::ui::icons::FOLDER,
|
||||
label: "Open",
|
||||
desc: "Open an existing file",
|
||||
command: "OPEN",
|
||||
},
|
||||
MenuItem {
|
||||
icon: "💾",
|
||||
icon: crate::ui::icons::SAVE,
|
||||
label: "Save",
|
||||
desc: "Save the current drawing",
|
||||
command: "SAVE",
|
||||
},
|
||||
MenuItem {
|
||||
icon: "📄",
|
||||
icon: crate::ui::icons::DOC,
|
||||
label: "Save As",
|
||||
desc: "Save as DWG or DXF",
|
||||
command: "SAVEAS",
|
||||
},
|
||||
MenuItem {
|
||||
icon: "🖨",
|
||||
icon: crate::ui::icons::PRINT,
|
||||
label: "Print",
|
||||
desc: "Print or plot the drawing",
|
||||
command: "PRINT",
|
||||
},
|
||||
MenuItem {
|
||||
icon: "⚙",
|
||||
icon: crate::ui::icons::GEAR,
|
||||
label: "Options",
|
||||
desc: "Open application settings",
|
||||
command: "OPTIONS",
|
||||
},
|
||||
MenuItem {
|
||||
icon: "❓",
|
||||
icon: crate::ui::icons::HELP,
|
||||
label: "Help",
|
||||
desc: "Open help documentation",
|
||||
command: "HELP",
|
||||
@@ -336,7 +336,7 @@ impl AppMenu {
|
||||
fn menu_item_btn<'a>(item: &'a MenuItem) -> Element<'a, Message> {
|
||||
button(
|
||||
row![
|
||||
container(text(item.icon).size(16).color(ICON_COLOR))
|
||||
container(crate::ui::icons::tinted(item.icon, 16.0, ICON_COLOR))
|
||||
.width(32)
|
||||
.padding([0, 4]),
|
||||
column![
|
||||
@@ -386,7 +386,8 @@ fn recent_item_btn(path: &PathBuf) -> Element<'_, Message> {
|
||||
let path_str = path.display().to_string();
|
||||
button(
|
||||
row![
|
||||
container(text("📄").size(13).color(ICON_COLOR)).width(24),
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::DOC, 13.0, ICON_COLOR))
|
||||
.width(24),
|
||||
column![
|
||||
text(name).size(11).color(Color::WHITE),
|
||||
text(type_label).size(9).color(DIM_COLOR),
|
||||
|
||||
@@ -125,7 +125,7 @@ pub fn color_selector<'a>(
|
||||
row![
|
||||
swatch(cur_bg),
|
||||
text(cur_name).size(11).color(TEXT),
|
||||
text(if open { " ▲" } else { " ▾" }).size(9).color(TEXT),
|
||||
crate::ui::icons::arrow_toggle(open, 9.0, TEXT),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
|
||||
@@ -397,13 +397,13 @@ impl CommandLine {
|
||||
// full backlog (everything pushed since the app started) so the
|
||||
// user can recover anything that has already faded off the
|
||||
// overlay.
|
||||
let dropdown_label = if self.history_open { "▾" } else { "▸" };
|
||||
let dropdown_btn = button(
|
||||
text(dropdown_label)
|
||||
.size(11)
|
||||
.color(PROMPT_COLOR),
|
||||
)
|
||||
.on_press(Message::CommandHistoryToggle)
|
||||
let dropdown_icon = if self.history_open {
|
||||
crate::ui::icons::arrow_down(11.0, PROMPT_COLOR)
|
||||
} else {
|
||||
crate::ui::icons::arrow_right(11.0, PROMPT_COLOR)
|
||||
};
|
||||
let dropdown_btn = button(dropdown_icon)
|
||||
.on_press(Message::CommandHistoryToggle)
|
||||
.style(|_: &Theme, _status| button::Style {
|
||||
background: Some(Background::Color(INPUT_ROW_BG)),
|
||||
text_color: Color::WHITE,
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
//! Shared monochrome UI-chrome icons rendered from bundled SVGs.
|
||||
//!
|
||||
//! Dropdown carets and the undo/redo controls used to be drawn as Unicode
|
||||
//! glyphs (`▾`, `▲`, `↶`, `↷`). Those depend on the active text font carrying
|
||||
//! the glyph: on desktop the system fallback fonts supply them, but the web
|
||||
//! build bundles only Fira Sans, which lacks them, so they rendered as empty
|
||||
//! boxes. Drawing them from SVG instead makes the chrome font-independent.
|
||||
|
||||
use iced::widget::{container, svg, Space};
|
||||
use iced::{Color, Element, Length, Theme};
|
||||
|
||||
const TRI_DOWN: &[u8] = include_bytes!("../../assets/icons/ui/tri_down.svg");
|
||||
const TRI_UP: &[u8] = include_bytes!("../../assets/icons/ui/tri_up.svg");
|
||||
const TRI_RIGHT: &[u8] = include_bytes!("../../assets/icons/ui/tri_right.svg");
|
||||
const UNDO: &[u8] = include_bytes!("../../assets/icons/ui/undo.svg");
|
||||
const REDO: &[u8] = include_bytes!("../../assets/icons/ui/redo.svg");
|
||||
|
||||
const LAY_ON: &[u8] = include_bytes!("../../assets/icons/layers/layon.svg");
|
||||
const LAY_OFF: &[u8] = include_bytes!("../../assets/icons/layers/layoff.svg");
|
||||
const LAY_FRZ: &[u8] = include_bytes!("../../assets/icons/layers/layfrz.svg");
|
||||
const LAY_THW: &[u8] = include_bytes!("../../assets/icons/layers/laythw.svg");
|
||||
const LAY_LCK: &[u8] = include_bytes!("../../assets/icons/layers/laylck.svg");
|
||||
const LAY_ULK: &[u8] = include_bytes!("../../assets/icons/layers/layulk.svg");
|
||||
|
||||
// Monochrome chrome glyphs (replace Unicode glyphs in buttons / menus / toolbars).
|
||||
// All are black-on-transparent; recolour them at the call site with [`tinted`].
|
||||
pub const CHECK: &[u8] = include_bytes!("../../assets/icons/ui/check.svg");
|
||||
pub const CLOSE: &[u8] = include_bytes!("../../assets/icons/ui/close.svg");
|
||||
pub const PLUS: &[u8] = include_bytes!("../../assets/icons/ui/plus.svg");
|
||||
pub const TRASH: &[u8] = include_bytes!("../../assets/icons/ui/trash.svg");
|
||||
pub const MENU: &[u8] = include_bytes!("../../assets/icons/ui/menu.svg");
|
||||
pub const BOLT: &[u8] = include_bytes!("../../assets/icons/ui/bolt.svg");
|
||||
pub const MOVE: &[u8] = include_bytes!("../../assets/icons/ui/move.svg");
|
||||
pub const SPLIT_V: &[u8] = include_bytes!("../../assets/icons/ui/split_v.svg");
|
||||
pub const SPLIT_H: &[u8] = include_bytes!("../../assets/icons/ui/split_h.svg");
|
||||
pub const UP: &[u8] = include_bytes!("../../assets/icons/ui/up.svg");
|
||||
pub const DOC_NEW: &[u8] = include_bytes!("../../assets/icons/ui/doc_new.svg");
|
||||
pub const DOC: &[u8] = include_bytes!("../../assets/icons/ui/doc.svg");
|
||||
pub const FOLDER: &[u8] = include_bytes!("../../assets/icons/ui/folder.svg");
|
||||
pub const SAVE: &[u8] = include_bytes!("../../assets/icons/ui/save.svg");
|
||||
pub const PRINT: &[u8] = include_bytes!("../../assets/icons/ui/print.svg");
|
||||
pub const GEAR: &[u8] = include_bytes!("../../assets/icons/ui/gear.svg");
|
||||
pub const HELP: &[u8] = include_bytes!("../../assets/icons/ui/help.svg");
|
||||
pub const HEART: &[u8] = include_bytes!("../../assets/icons/ui/heart.svg");
|
||||
pub const DOT: &[u8] = include_bytes!("../../assets/icons/ui/dot.svg");
|
||||
pub const TRI_LEFT_B: &[u8] = include_bytes!("../../assets/icons/ui/tri_left.svg");
|
||||
pub const ARROW_LONG_RIGHT: &[u8] = include_bytes!("../../assets/icons/ui/arrow_long_right.svg");
|
||||
|
||||
/// Render one of the bundled SVGs tinted to `color` at a square `size`.
|
||||
pub fn tinted<'a, M: 'a>(bytes: &'static [u8], size: f32, color: Color) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(move |_: &Theme, _| svg::Style { color: Some(color) })
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Backwards-compatible alias used by the caret/undo/redo helpers below.
|
||||
fn icon<'a, M: 'a>(bytes: &'static [u8], size: f32, color: Color) -> Element<'a, M> {
|
||||
tinted(bytes, size, color)
|
||||
}
|
||||
|
||||
/// A fixed-width (14 px) "current row" check column: a green-tintable check
|
||||
/// when `active`, otherwise an empty spacer that preserves alignment. Used by
|
||||
/// the many dropdown / popup list rows that mark the selected entry.
|
||||
pub fn check_cell<'a, M: 'a>(active: bool, color: Color) -> Element<'a, M> {
|
||||
let inner: Element<'a, M> = if active {
|
||||
tinted(CHECK, 11.0, color)
|
||||
} else {
|
||||
Space::new().width(0).into()
|
||||
};
|
||||
container(inner).width(Length::Fixed(14.0)).into()
|
||||
}
|
||||
|
||||
/// Render a bundled SVG at its native colours (no tint) at a square `size`.
|
||||
pub fn raw<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Layer visibility icon bytes (on / off).
|
||||
pub fn layer_visible(visible: bool) -> &'static [u8] {
|
||||
if visible {
|
||||
LAY_ON
|
||||
} else {
|
||||
LAY_OFF
|
||||
}
|
||||
}
|
||||
|
||||
/// Layer freeze icon bytes (frozen / thawed).
|
||||
pub fn layer_freeze(frozen: bool) -> &'static [u8] {
|
||||
if frozen {
|
||||
LAY_FRZ
|
||||
} else {
|
||||
LAY_THW
|
||||
}
|
||||
}
|
||||
|
||||
/// Layer lock icon bytes (locked / unlocked).
|
||||
pub fn layer_lock(locked: bool) -> &'static [u8] {
|
||||
if locked {
|
||||
LAY_LCK
|
||||
} else {
|
||||
LAY_ULK
|
||||
}
|
||||
}
|
||||
|
||||
/// Downward dropdown caret (replaces `▾`).
|
||||
pub fn arrow_down<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_DOWN, size, color)
|
||||
}
|
||||
|
||||
/// Upward dropdown caret, shown when a dropdown is open (replaces `▲`).
|
||||
pub fn arrow_up<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_UP, size, color)
|
||||
}
|
||||
|
||||
/// Rightward caret for a collapsed item (replaces `▸`).
|
||||
pub fn arrow_right<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_RIGHT, size, color)
|
||||
}
|
||||
|
||||
/// Caret that flips up/down with `open`.
|
||||
pub fn arrow_toggle<'a, M: 'a>(open: bool, size: f32, color: Color) -> Element<'a, M> {
|
||||
if open {
|
||||
arrow_up(size, color)
|
||||
} else {
|
||||
arrow_down(size, color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Undo curved arrow (replaces `↶`).
|
||||
pub fn undo<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(UNDO, size, color)
|
||||
}
|
||||
|
||||
/// Redo curved arrow (replaces `↷`).
|
||||
pub fn redo<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(REDO, size, color)
|
||||
}
|
||||
@@ -170,9 +170,19 @@ impl LayerPanel {
|
||||
// ── Toolbar ───────────────────────────────────────────────────────
|
||||
let toolbar = container(
|
||||
row![
|
||||
toolbar_btn("⊕ New", Message::LayerNew),
|
||||
toolbar_btn_cond("⊗ Delete", Message::LayerDelete, has_sel && !sel_is_zero),
|
||||
toolbar_btn_cond("✓ Set Current", Message::LayerSetCurrent, has_sel),
|
||||
toolbar_btn(crate::ui::icons::PLUS, "New", Message::LayerNew),
|
||||
toolbar_btn_cond(
|
||||
crate::ui::icons::TRASH,
|
||||
"Delete",
|
||||
Message::LayerDelete,
|
||||
has_sel && !sel_is_zero,
|
||||
),
|
||||
toolbar_btn_cond(
|
||||
crate::ui::icons::CHECK,
|
||||
"Set Current",
|
||||
Message::LayerSetCurrent,
|
||||
has_sel,
|
||||
),
|
||||
]
|
||||
.spacing(2),
|
||||
)
|
||||
@@ -289,9 +299,16 @@ impl LayerPanel {
|
||||
|
||||
// ── Toolbar buttons ───────────────────────────────────────────────────────
|
||||
|
||||
fn toolbar_btn(label: &str, msg: Message) -> Element<'_, Message> {
|
||||
button(text(label).size(11).color(Color::WHITE))
|
||||
.on_press(msg)
|
||||
fn toolbar_btn<'a>(icon: &'static [u8], label: &'a str, msg: Message) -> Element<'a, Message> {
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::tinted(icon, 12.0, Color::WHITE),
|
||||
text(label).size(11).color(Color::WHITE),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
@@ -325,8 +342,13 @@ fn toolbar_btn(label: &str, msg: Message) -> Element<'_, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn toolbar_btn_cond(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
||||
let mut b = button(text(label).size(11).color(if enabled {
|
||||
fn toolbar_btn_cond<'a>(
|
||||
icon: &'static [u8],
|
||||
label: &'a str,
|
||||
msg: Message,
|
||||
enabled: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let fg = if enabled {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color {
|
||||
@@ -335,7 +357,15 @@ fn toolbar_btn_cond(label: &str, msg: Message, enabled: bool) -> Element<'_, Mes
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
}
|
||||
}))
|
||||
};
|
||||
let mut b = button(
|
||||
row![
|
||||
crate::ui::icons::tinted(icon, 12.0, fg),
|
||||
text(label).size(11).color(fg),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
@@ -431,42 +461,32 @@ fn layer_row<'a>(
|
||||
.into()
|
||||
};
|
||||
|
||||
let vis_svg: &'static [u8] = if layer.visible {
|
||||
include_bytes!("../../assets/icons/layers/layon.svg")
|
||||
} else {
|
||||
include_bytes!("../../assets/icons/layers/layoff.svg")
|
||||
};
|
||||
let frz_svg: &'static [u8] = if layer.frozen {
|
||||
include_bytes!("../../assets/icons/layers/layfrz.svg")
|
||||
} else {
|
||||
include_bytes!("../../assets/icons/layers/laythw.svg")
|
||||
};
|
||||
let lck_svg: &'static [u8] = if layer.locked {
|
||||
include_bytes!("../../assets/icons/layers/laylck.svg")
|
||||
} else {
|
||||
include_bytes!("../../assets/icons/layers/layulk.svg")
|
||||
};
|
||||
let vis_svg = crate::ui::icons::layer_visible(layer.visible);
|
||||
let frz_svg = crate::ui::icons::layer_freeze(layer.frozen);
|
||||
let lck_svg = crate::ui::icons::layer_lock(layer.locked);
|
||||
|
||||
let status_dot: Element<'_, Message> = if is_current {
|
||||
text("✓")
|
||||
.size(13)
|
||||
.color(Color {
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::CHECK,
|
||||
13.0,
|
||||
Color {
|
||||
r: 0.25,
|
||||
g: 0.85,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
})
|
||||
.into()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
text("▣")
|
||||
.size(11)
|
||||
.color(Color {
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::DOT,
|
||||
9.0,
|
||||
Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
})
|
||||
.into()
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
// Name cell
|
||||
@@ -693,11 +713,7 @@ fn layer_row<'a>(
|
||||
// Per-viewport freeze columns
|
||||
for (vp_idx, _vp_col) in vp_cols.iter().enumerate() {
|
||||
let is_vp_frozen = layer.vp_frozen.get(vp_idx).copied().unwrap_or(false);
|
||||
let vp_frz_svg: &'static [u8] = if is_vp_frozen {
|
||||
include_bytes!("../../assets/icons/layers/layfrz.svg")
|
||||
} else {
|
||||
include_bytes!("../../assets/icons/layers/laythw.svg")
|
||||
};
|
||||
let vp_frz_svg = crate::ui::icons::layer_freeze(is_vp_frozen);
|
||||
row_content = row_content.push(
|
||||
container(svg_btn(
|
||||
vp_frz_svg,
|
||||
|
||||
@@ -194,14 +194,28 @@ pub fn view_window<'a>(
|
||||
})
|
||||
.padding([4, 10]),
|
||||
Space::new().width(Fill),
|
||||
button(text("◀ Move Left").size(11))
|
||||
.on_press(Message::LayoutManagerMoveLeft)
|
||||
.style(btn_s(false))
|
||||
.padding([4, 8]),
|
||||
button(text("Move Right ▶").size(11))
|
||||
.on_press(Message::LayoutManagerMoveRight)
|
||||
.style(btn_s(false))
|
||||
.padding([4, 8]),
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::TRI_LEFT_B, 9.0, Color::WHITE),
|
||||
text("Move Left").size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::LayoutManagerMoveLeft)
|
||||
.style(btn_s(false))
|
||||
.padding([4, 8]),
|
||||
button(
|
||||
row![
|
||||
text("Move Right").size(11),
|
||||
crate::ui::icons::arrow_right(9.0, Color::WHITE),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::LayoutManagerMoveRight)
|
||||
.style(btn_s(false))
|
||||
.padding([4, 8]),
|
||||
button(text("Set Current").size(11))
|
||||
.on_press(Message::LayoutManagerSetCurrent)
|
||||
.style(btn_s(true))
|
||||
@@ -223,12 +237,17 @@ pub fn view_window<'a>(
|
||||
.map(|name| {
|
||||
let is_sel = name.as_str() == selected;
|
||||
let is_cur = name.as_str() == current.as_str();
|
||||
let label = if is_cur {
|
||||
format!("{name} ◀")
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
button(text(label).size(12))
|
||||
let mut item_row = row![text(name.clone()).size(12)]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
if is_cur {
|
||||
item_row = item_row.push(crate::ui::icons::tinted(
|
||||
crate::ui::icons::TRI_LEFT_B,
|
||||
8.0,
|
||||
Color::WHITE,
|
||||
));
|
||||
}
|
||||
button(item_row)
|
||||
.on_press(Message::LayoutManagerSelect(name.clone()))
|
||||
.style(list_item(is_sel))
|
||||
.padding([5, 10])
|
||||
@@ -286,7 +305,7 @@ pub fn view_window<'a>(
|
||||
row![
|
||||
text("Status:").size(11).color(DIM).width(80),
|
||||
text(if selected == current.as_str() {
|
||||
"Active ◀"
|
||||
"Active"
|
||||
} else {
|
||||
"Inactive"
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod color_select;
|
||||
pub mod command_line;
|
||||
pub mod cycle_popup;
|
||||
pub mod dimstyle;
|
||||
pub mod icons;
|
||||
pub mod isolate_popup;
|
||||
pub mod layers;
|
||||
pub mod layout_manager;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! so both stack dialogs here — one code path for every platform.
|
||||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, mouse_area, opaque, row, stack, text};
|
||||
use iced::widget::{button, column, container, mouse_area, opaque, row, stack};
|
||||
use iced::{Background, Border, Color, Element, Length, Padding, Theme, Vector};
|
||||
|
||||
const PANEL: Color = Color {
|
||||
@@ -49,17 +49,26 @@ pub fn modal<'a>(
|
||||
on_close: Message,
|
||||
offset: Vector,
|
||||
) -> Element<'a, Message> {
|
||||
let close = button(text("✕").size(15))
|
||||
.on_press(on_close)
|
||||
.padding([1, 7])
|
||||
.style(close_style);
|
||||
let close = button(crate::ui::icons::tinted(
|
||||
crate::ui::icons::CLOSE,
|
||||
13.0,
|
||||
Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
},
|
||||
))
|
||||
.on_press(on_close)
|
||||
.padding([1, 7])
|
||||
.style(close_style);
|
||||
|
||||
// Draggable title bar: a grip handle next to the ✕. Kept `Shrink` (no
|
||||
// `Fill`) so a single Fill child can't blow the dialog out to the full
|
||||
// screen width — the dialog stays sized to its content. Pressing the grip
|
||||
// starts a drag (handled in `update`).
|
||||
let grip = mouse_area(
|
||||
container(text("✥").size(15).color(GRIP_C))
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::MOVE, 14.0, GRIP_C))
|
||||
.padding([1, 7])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TITLE_C)),
|
||||
|
||||
@@ -329,12 +329,12 @@ pub fn view_window<'a>(
|
||||
hdivider(),
|
||||
section_label("Position"),
|
||||
button(
|
||||
text(if center {
|
||||
"✓ Center on page"
|
||||
} else {
|
||||
" Center on page"
|
||||
})
|
||||
.size(11)
|
||||
row![
|
||||
crate::ui::icons::check_cell(center, Color::WHITE),
|
||||
text("Center on page").size(11),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center)
|
||||
)
|
||||
.on_press(Message::PageSetupCenterToggle)
|
||||
.style(pill(center))
|
||||
|
||||
@@ -199,6 +199,27 @@ fn pill_button<'a>(label: &str, msg: Message, bg: Color) -> Element<'a, Message>
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Square icon variant of [`pill_button`] for glyph-free actions (e.g. remove).
|
||||
fn pill_icon_button<'a>(icon: &'static [u8], msg: Message, bg: Color) -> Element<'a, Message> {
|
||||
button(crate::ui::icons::tinted(icon, 11.0, WHITE))
|
||||
.padding([5, 9])
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| {
|
||||
let c = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: bg.r + 0.08, g: bg.g + 0.08, b: bg.b + 0.08, a: 1.0 }
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(c)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
const GREEN: Color = Color { r: 0.2, g: 0.45, b: 0.28, a: 1.0 };
|
||||
const RED: Color = Color { r: 0.4, g: 0.25, b: 0.25, a: 1.0 };
|
||||
|
||||
@@ -229,7 +250,11 @@ fn install_controls<'a>(
|
||||
.spacing(4);
|
||||
if removable {
|
||||
controls = controls.push(Space::new().width(6));
|
||||
controls = controls.push(pill_button("✕", Message::PluginRepoRemove(repo_s), RED));
|
||||
controls = controls.push(pill_icon_button(
|
||||
crate::ui::icons::CLOSE,
|
||||
Message::PluginRepoRemove(repo_s),
|
||||
RED,
|
||||
));
|
||||
}
|
||||
controls.into()
|
||||
}
|
||||
|
||||
@@ -676,13 +676,14 @@ pub fn color_picker_dropdown<'a>(
|
||||
|
||||
// "More Colors…" toggle button
|
||||
let more_btn = button(
|
||||
text(if palette_open {
|
||||
"▲ Less"
|
||||
} else {
|
||||
"▼ More Colors…"
|
||||
})
|
||||
.size(10)
|
||||
.color(HINT_COLOR),
|
||||
row![
|
||||
crate::ui::icons::arrow_toggle(palette_open, 9.0, HINT_COLOR),
|
||||
text(if palette_open { "Less" } else { "More Colors…" })
|
||||
.size(10)
|
||||
.color(HINT_COLOR),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(palette_toggle_msg)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
|
||||
@@ -234,20 +234,8 @@ impl Ribbon {
|
||||
|
||||
// ── Tab buttons ───────────────────────────────────────────────────
|
||||
let history_controls = row![
|
||||
render_history_control(
|
||||
"↶",
|
||||
"Undo",
|
||||
UNDO_HISTORY_ID,
|
||||
undo_count,
|
||||
&self.open_dropdown
|
||||
),
|
||||
render_history_control(
|
||||
"↷",
|
||||
"Redo",
|
||||
REDO_HISTORY_ID,
|
||||
redo_count,
|
||||
&self.open_dropdown
|
||||
),
|
||||
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown),
|
||||
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown),
|
||||
]
|
||||
.spacing(TOP_HIST_GAP)
|
||||
.align_y(iced::Center);
|
||||
@@ -609,14 +597,13 @@ impl Ribbon {
|
||||
.iter()
|
||||
.map(|(cmd, label, item_icon)| {
|
||||
let is_current = *cmd == last_cmd;
|
||||
let checkmark = text(if is_current { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if is_current {
|
||||
CHECK_COLOR
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})
|
||||
.width(Length::Fixed(14.0));
|
||||
let checkmark: Element<'_, Message> = container(if is_current {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let icon_el: Element<Message> = match *item_icon {
|
||||
IconKind::Glyph(s) => text(s)
|
||||
.size(13)
|
||||
@@ -717,59 +704,16 @@ impl Ribbon {
|
||||
.width(12)
|
||||
.height(12);
|
||||
|
||||
let vis = text(if lv { "●" } else { "○" }).size(10).color(if lv {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.85,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
let vis = crate::ui::icons::raw(crate::ui::icons::layer_visible(lv), 14.0);
|
||||
let freeze = crate::ui::icons::raw(crate::ui::icons::layer_freeze(lf), 14.0);
|
||||
let lock = crate::ui::icons::raw(crate::ui::icons::layer_lock(ll), 14.0);
|
||||
let checkmark: Element<'_, Message> = container(if is_active {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
}
|
||||
});
|
||||
let freeze = text("✱").size(10).color(if lf {
|
||||
Color {
|
||||
r: 0.40,
|
||||
g: 0.80,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.85,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
});
|
||||
let lock = text(if ll { "🔒" } else { "🔓" }).size(10).color(if ll {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.70,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
}
|
||||
});
|
||||
let checkmark = text(if is_active { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if is_active {
|
||||
CHECK_COLOR
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})
|
||||
.width(Length::Fixed(14.0));
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let label =
|
||||
text(&info.name)
|
||||
.size(11)
|
||||
@@ -893,14 +837,13 @@ impl Ribbon {
|
||||
.into_iter()
|
||||
.map(|lt| {
|
||||
let is_cur = lt.name == *active_lt;
|
||||
let check = text(if is_cur { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if is_cur {
|
||||
CHECK_COLOR
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})
|
||||
.width(Length::Fixed(14.0));
|
||||
let check: Element<'_, Message> = container(if is_cur {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let name_col = text(lt.name.clone())
|
||||
.size(11)
|
||||
.color(if is_cur { LABEL_ON } else { LABEL_OFF })
|
||||
@@ -971,14 +914,13 @@ impl Ribbon {
|
||||
.map(|item| {
|
||||
let is_cur = item.0 == active_lw;
|
||||
let label = item.to_string();
|
||||
let check = text(if is_cur { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if is_cur {
|
||||
CHECK_COLOR
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})
|
||||
.width(Length::Fixed(14.0));
|
||||
let check: Element<'_, Message> = container(if is_cur {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
button(
|
||||
row![
|
||||
check,
|
||||
|
||||
@@ -11,6 +11,7 @@ use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::modules::{IconKind, ModuleEvent, RibbonGroup, RibbonItem, StyleKey, ToolDef};
|
||||
use crate::ui::icons;
|
||||
use crate::ui::properties::{acad_color_display, LwItem};
|
||||
|
||||
use super::LayerInfo;
|
||||
@@ -361,7 +362,7 @@ pub(super) fn render_small<'a>(
|
||||
|
||||
let arr_tip = format!("{} options", cur_label);
|
||||
let arr_btn = button(
|
||||
container(text("▾").size(7).color(ARROW_COLOR))
|
||||
container(icons::arrow_down(8.0, ARROW_COLOR))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
@@ -513,7 +514,7 @@ pub(super) fn render_large<'a>(
|
||||
});
|
||||
|
||||
let arr_btn = button(
|
||||
container(text("▾").size(9).color(ARROW_COLOR))
|
||||
container(icons::arrow_down(9.0, ARROW_COLOR))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
@@ -562,51 +563,9 @@ pub(super) fn render_large<'a>(
|
||||
let ll = info.map(|l| l.locked).unwrap_or(false);
|
||||
let is_open = open_dd.as_deref() == Some(LAYER_COMBO_ID);
|
||||
|
||||
let vis_icon = text(if lv { "●" } else { "○" }).size(10).color(if lv {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.85,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
}
|
||||
});
|
||||
let freeze_icon = text("✱").size(10).color(if lf {
|
||||
Color {
|
||||
r: 0.40,
|
||||
g: 0.80,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.85,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
});
|
||||
let lock_icon = text(if ll { "🔒" } else { "🔓" }).size(10).color(if ll {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.70,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
}
|
||||
});
|
||||
let vis_icon = icons::raw(icons::layer_visible(lv), 14.0);
|
||||
let freeze_icon = icons::raw(icons::layer_freeze(lf), 14.0);
|
||||
let lock_icon = icons::raw(icons::layer_lock(ll), 14.0);
|
||||
let swatch = container(text(""))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(lc)),
|
||||
@@ -625,7 +584,7 @@ pub(super) fn render_large<'a>(
|
||||
.width(12)
|
||||
.height(12);
|
||||
|
||||
const ICONS_USED: f32 = 10.0 + 10.0 + 10.0 + 12.0 + 10.0 + 5.0 * 4.0 + 16.0 + 16.0;
|
||||
const ICONS_USED: f32 = 14.0 + 14.0 + 14.0 + 12.0 + 10.0 + 5.0 * 4.0 + 16.0 + 16.0;
|
||||
let name_w = (COMBO_W - ICONS_USED).max(40.0);
|
||||
|
||||
let combo_btn = button(
|
||||
@@ -637,12 +596,15 @@ pub(super) fn render_large<'a>(
|
||||
container(text(active_layer).size(11).color(Color::WHITE))
|
||||
.width(name_w)
|
||||
.clip(true),
|
||||
text("▾").size(9).color(Color {
|
||||
r: 0.7,
|
||||
g: 0.7,
|
||||
b: 0.7,
|
||||
a: 1.0
|
||||
}),
|
||||
icons::arrow_down(
|
||||
9.0,
|
||||
Color {
|
||||
r: 0.7,
|
||||
g: 0.7,
|
||||
b: 0.7,
|
||||
a: 1.0,
|
||||
},
|
||||
),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
@@ -801,12 +763,16 @@ pub(super) fn render_large<'a>(
|
||||
container(text(label).size(10).color(Color::WHITE))
|
||||
.width(Fill)
|
||||
.clip(true),
|
||||
text(if is_open { "▲" } else { "▼" }).size(7).color(Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0
|
||||
}),
|
||||
icons::arrow_toggle(
|
||||
is_open,
|
||||
8.0,
|
||||
Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
},
|
||||
),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
@@ -905,12 +871,16 @@ pub(super) fn render_large<'a>(
|
||||
container(text(active.clone()).size(11).color(Color::WHITE))
|
||||
.width(Fill)
|
||||
.clip(true),
|
||||
text(if is_open { "▲" } else { "▾" }).size(9).color(Color {
|
||||
r: 0.7,
|
||||
g: 0.7,
|
||||
b: 0.7,
|
||||
a: 1.0
|
||||
}),
|
||||
icons::arrow_toggle(
|
||||
is_open,
|
||||
9.0,
|
||||
Color {
|
||||
r: 0.7,
|
||||
g: 0.7,
|
||||
b: 0.7,
|
||||
a: 1.0,
|
||||
},
|
||||
),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
@@ -971,18 +941,21 @@ pub(super) fn render_large<'a>(
|
||||
let key = style_key;
|
||||
button(
|
||||
row![
|
||||
text(if is_sel { "✓" } else { " " })
|
||||
.size(10)
|
||||
.color(if is_sel {
|
||||
container(if is_sel {
|
||||
icons::tinted(
|
||||
icons::CHECK,
|
||||
10.0,
|
||||
Color {
|
||||
r: 0.2,
|
||||
g: 0.8,
|
||||
b: 0.4,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
}),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(12),
|
||||
text(name).size(11).color(Color::WHITE),
|
||||
]
|
||||
.spacing(6)
|
||||
@@ -1281,7 +1254,6 @@ pub fn module_event_to_message(event: ModuleEvent) -> Message {
|
||||
// ── History control ────────────────────────────────────────────────────────
|
||||
|
||||
pub(super) fn render_history_control<'a>(
|
||||
glyph: &'static str,
|
||||
label: &'static str,
|
||||
dropdown_id: &'static str,
|
||||
count: usize,
|
||||
@@ -1289,12 +1261,20 @@ pub(super) fn render_history_control<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
let dd_open = open_dropdown.as_deref() == Some(dropdown_id);
|
||||
let active = count > 0;
|
||||
let icon_color = if active { Color::WHITE } else { LABEL_OFF };
|
||||
|
||||
let main_btn = {
|
||||
let glyph = if dropdown_id == UNDO_HISTORY_ID {
|
||||
icons::undo(15.0, icon_color)
|
||||
} else {
|
||||
icons::redo(15.0, icon_color)
|
||||
};
|
||||
let btn = button(
|
||||
text(glyph)
|
||||
.size(14)
|
||||
.color(if active { Color::WHITE } else { LABEL_OFF }),
|
||||
container(glyph)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(move |_: &Theme, status| top_hist_btn_style(active, dd_open, status))
|
||||
.width(Length::Fixed(TOP_HIST_W))
|
||||
@@ -1321,11 +1301,10 @@ pub(super) fn render_history_control<'a>(
|
||||
|
||||
let arrow_btn = {
|
||||
let btn = button(
|
||||
container(
|
||||
text("▾")
|
||||
.size(7)
|
||||
.color(if active { ARROW_COLOR } else { LABEL_OFF }),
|
||||
)
|
||||
container(icons::arrow_down(
|
||||
8.0,
|
||||
if active { ARROW_COLOR } else { LABEL_OFF },
|
||||
))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
|
||||
@@ -70,14 +70,7 @@ pub fn scale_popup_overlay(
|
||||
}
|
||||
|
||||
fn scale_row(label: String, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = text(if active { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if active {
|
||||
CHECK_COLOR
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})
|
||||
.width(Length::Fixed(14.0));
|
||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
|
||||
@@ -60,10 +60,7 @@ pub fn selection_filter_popup_overlay(
|
||||
}
|
||||
|
||||
fn type_row(name: String, included: bool) -> Element<'static, Message> {
|
||||
let check = text(if included { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if included { CHECK_COLOR } else { Color::TRANSPARENT })
|
||||
.width(Length::Fixed(14.0));
|
||||
let check = crate::ui::icons::check_cell(included, CHECK_COLOR);
|
||||
|
||||
let lbl = text(name.clone())
|
||||
.size(11)
|
||||
|
||||
@@ -107,14 +107,7 @@ fn snap_row<'a>(
|
||||
label: &'a str,
|
||||
active: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let checkmark = text(if active { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if active {
|
||||
CHECK_COLOR
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})
|
||||
.width(Length::Fixed(14.0));
|
||||
let checkmark = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
|
||||
let icon_el = text(icon)
|
||||
.size(11)
|
||||
|
||||
@@ -76,7 +76,7 @@ impl StatusBar {
|
||||
// Which pills the user has chosen to show on the bar.
|
||||
config: &'a StatusBarConfig,
|
||||
) -> Element<'a, Message> {
|
||||
let menu_btn = button(text("≡").size(14).color(ICON_COLOR))
|
||||
let menu_btn = button(crate::ui::icons::tinted(crate::ui::icons::MENU, 14.0, ICON_COLOR))
|
||||
.on_press(Message::Command("MENU".into()))
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
@@ -285,7 +285,7 @@ fn format_coords(p: glam::Vec3) -> String {
|
||||
// ── Customization handle ──────────────────────────────────────────────────
|
||||
|
||||
fn customize_btn() -> Element<'static, Message> {
|
||||
button(text("≡").size(14).color(ICON_COLOR))
|
||||
button(crate::ui::icons::tinted(crate::ui::icons::MENU, 14.0, ICON_COLOR))
|
||||
.on_press(Message::ToggleStatusBarMenu)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
@@ -466,8 +466,15 @@ fn osnap_btn(active: bool, snap_enabled: bool, open: bool) -> Element<'static, M
|
||||
OSNAP_OFF_TEXT
|
||||
};
|
||||
|
||||
let left = button(text("⚡ OSNAP").size(10).color(text_color))
|
||||
.on_press(Message::ToggleSnapEnabled)
|
||||
let left = button(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::BOLT, 11.0, text_color),
|
||||
text("OSNAP").size(10).color(text_color),
|
||||
]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleSnapEnabled)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => {
|
||||
@@ -495,7 +502,7 @@ fn osnap_btn(active: bool, snap_enabled: bool, open: bool) -> Element<'static, M
|
||||
})
|
||||
.padding([2, 6]);
|
||||
|
||||
let right = button(text("▾").size(9).color(text_color))
|
||||
let right = button(crate::ui::icons::arrow_down(9.0, text_color))
|
||||
.on_press(Message::ToggleSnapPopup)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
@@ -607,12 +614,16 @@ fn space_tab<'a>(
|
||||
.padding([2, 6])
|
||||
.width(Length::Fixed(90.0));
|
||||
|
||||
let cancel_btn = button(text("✕").size(10).color(Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
}))
|
||||
let cancel_btn = button(crate::ui::icons::tinted(
|
||||
crate::ui::icons::CLOSE,
|
||||
10.0,
|
||||
Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
},
|
||||
))
|
||||
.on_press(Message::LayoutRenameCancel)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
|
||||
@@ -52,10 +52,7 @@ pub fn statusbar_menu_overlay(config: &StatusBarConfig) -> Element<'static, Mess
|
||||
}
|
||||
|
||||
fn menu_row(label: &'static str, checked: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = text(if checked { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if checked { CHECK_COLOR } else { Color::TRANSPARENT })
|
||||
.width(Length::Fixed(14.0));
|
||||
let check = crate::ui::icons::check_cell(checked, CHECK_COLOR);
|
||||
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
|
||||
@@ -61,12 +61,8 @@ pub fn item<'a>(
|
||||
} else {
|
||||
// Fixed-width ✓ column keeps every name left-aligned whether or not the
|
||||
// row is current.
|
||||
let check = if is_current {
|
||||
text("✓").size(11).color(CURRENT_CHECK)
|
||||
} else {
|
||||
text("").size(11)
|
||||
};
|
||||
let label = row![check.width(14), text(name.to_string()).size(11).color(TEXT)]
|
||||
let check = crate::ui::icons::check_cell(is_current, CURRENT_CHECK);
|
||||
let label = row![check, text(name.to_string()).size(11).color(TEXT)]
|
||||
.align_y(iced::Center);
|
||||
let cell = container(label)
|
||||
.padding([4, 8])
|
||||
|
||||
@@ -73,10 +73,7 @@ pub fn units_popup_overlay(current: i16) -> Element<'static, Message> {
|
||||
}
|
||||
|
||||
fn unit_row(label: &'static str, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = text(if active { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if active { CHECK_COLOR } else { Color::TRANSPARENT })
|
||||
.width(Length::Fixed(14.0));
|
||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
|
||||
@@ -109,7 +109,7 @@ fn render_notes_line<'a>(raw: &str) -> Element<'a, Message> {
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("- ").or_else(|| trimmed.strip_prefix("* ")) {
|
||||
return row![
|
||||
text("•").size(11).color(DIM).width(14),
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::DOT, 5.0, DIM)).width(14),
|
||||
text(strip_inline_md(rest)).size(11).color(WHITE),
|
||||
]
|
||||
.spacing(4)
|
||||
@@ -165,8 +165,12 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
false,
|
||||
);
|
||||
let latest_card = version_card("Latest", format!("v{}", latest), true);
|
||||
let arrow = container(text("→").size(22).color(DIM))
|
||||
.width(iced::Length::Fixed(32.0))
|
||||
let arrow = container(crate::ui::icons::tinted(
|
||||
crate::ui::icons::ARROW_LONG_RIGHT,
|
||||
20.0,
|
||||
DIM,
|
||||
))
|
||||
.width(iced::Length::Fixed(32.0))
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center);
|
||||
|
||||