Releases: roblillack/saudade
Releases · roblillack/saudade
v0.5.0
Added
- Menu items can be disabled.
MenuItem::with_enabledattaches a predicate
that is evaluated live on every paint and every attempt to fire, so a menu
built once tracks changing application state (typically read through an
Rc<RefCell<…>>). A disabled item renders greyed, shows no hover band, is
skipped by arrow-key navigation and mnemonics, and never fires — by mouse,
keyboard, or accelerator. (#46) - Menu items can show a checkmark.
MenuItem::with_checkedattaches a
predicate, also read live each paint, that draws a tick in the item's left
gutter while true — for toggles and radio-style groups. The glyph is the
same oneCheckboxuses, tinted to the item's current text color, and it
rides inside the existing label inset, so the layout never shifts between
checked and unchecked. The mark is display-only: a checked item still fires
its callback normally when picked. (#46) - Menu accelerators are now live, not just decorative.
MenuItem::with_accel
takes the newAcceltype (or its conventional string form:"Ctrl+R",
"Ctrl+Shift+Enter"), and pressing a matching chord while the bar is closed
fires the item directly.Accelnames its modifiers as platform-independent
roles (AccelMods):Accel::primary('r')means ⌘R on macOS and Ctrl+R
everywhere else, resolved through aModifierSchemeboth when matching
input and when rendering the right-aligned hint. The scheme defaults to the
build platform's;MenuBar::with_schemepins it, e.g. for snapshot tests
that must not drift between hosts. A chord whose only matches are disabled
items falls through unconsumed, keeping its ordinary meaning in the focused
widget (Ctrl+Left stays word-jump in an editor); while a menu is open it owns
the keyboard, so chords wait. Breaking:MenuItem::Action'saccelfield is
nowOption<Accel>instead ofOption<String>—with_accel("Ctrl+R")
call sites keep compiling viaFrom<&str>, which panics on a malformed
chord so a typo fails loudly at first use. (#46) - Text can now be drawn in three font families — sans-serif, serif, and
monospace — each in bold, italic, and bold-italic, all using
the host's real installed faces rather than a synthesized smear or shear of the
regular one. Two new enums name the axes:FontFamily(Sans/Serif/
Mono) andFontStyle(Regular/Bold/Italic/BoldItalic). Draw
withPainter::text_styled(.., family, style)and measure with
Painter::measure_text_styled(.., family, style); the existingtext/
measure_textstay sans-serif regular, so nothing already on screen moves. (#44) Font::load_sans(renamed fromload_system),Font::load_serif, and
Font::load_monospaceload the three families, walking — for the serif — the
classic Win 3.1 / Office serif families (Times New Roman, Georgia, …) down to
modern Linux replacements. Each loader now also loads the bold, italic, and
bold-italic faces of the chosen family alongside the regular one. Because real
bold / italic / serif faces carry their own advance widths, measuring in the
same family and style the text is drawn keeps word-wrap pixel-accurate. A
family the host ships without a given face falls back to the nearest real
face it does have (ultimately the regular face), and the loader verifies
fontdb's match actually carries the requested weight/slant so a regular face is
never passed off as bold or italic. (#44)- For deterministic, font-bundling snapshot tests,
Font::from_sans_bytes
(renamed fromfrom_bytes) gainswith_bold_bytes,with_italic_bytes, and
with_bold_italic_bytesbuilders to attach the emphasis faces from in-memory
buffers, andMockBackendgainswith_serif_fontalongsidewith_sans_font
(renamed fromwith_font) /with_mono_font. Painter::blit_argbblits a block of pre-composited, opaque ARGB pixels in
one call — the bulk path for drawing a decoded or composed image, where a
grid of per-pixelpixel()calls is the bottleneck. Each source pixel still
snaps to the same physical blockpixel()would produce and the physical
clip rect is honored, but the logical→physical snap runs once per row and
column instead of twice per pixel and the clip is resolved once for the whole
blit. Alpha is ignored: the source is assumed already flattened to opaque, so
no per-pixel blending happens. (#45)
Changed
- Breaking:
Painter::newandPainter::with_popup_anchornow take a single
FontSet { sans, serif, mono }in place of the two separateOption<&Font>
arguments. A backend builds oneFontSetfrom the fonts it owns; offscreen
painters with no text passFontSet::default(). This replaces three
same-typed positional font arguments (easy to transpose) with one named bundle
and makes room for the serif family. (#44) - Breaking renames:
Font::load_system→Font::load_sans,
Font::from_bytes→Font::from_sans_bytes, andMockBackend::with_font→
MockBackend::with_sans_font, so each name states the family it loads. (#44) - Breaking: the monospace-specific painter methods
mono_text,
measure_mono_text, andmono_cumulative_widthsare removed in favor of the
general family+style API: draw withtext_styled(.., FontFamily::Mono, FontStyle::Regular), measure withmeasure_text_styled(.., FontFamily::Mono, FontStyle::Regular), and get caret offsets with the new
Painter::cumulative_widths(text, size, family, style).Font::cumulative_widths
likewise gains aFontStyleargument. (#44)
Fixed
TextEditorandListno longer jump their scroll position back to the
caret / selected row when the window merely gains or loses focus. Their
layoutre-synced the scrollbar range and scrolled the caret/selection into
view, but a layout pass also fires for reasons unrelated to editing — on
Wayland the compositor sends aconfigure(hence a relayout) on every
activation change — so a wheel-scrolled view snapped back the instant focus
changed (visible in thenotepadexample as the caret jumping to the first
line).layoutnow only re-clamps the scroll range to the new viewport;
edits, keyboard navigation, and selection changes still scroll the
caret/selection into view as before. (#43)
v0.4.0
Added
ScrollBararrow buttons now behave like real push buttons. Clicking one
sinks it — a single dark top/left shadow line, no highlight, the arrow glyph
nudged 1px down-right — and holding it auto-repeats the line-step scroll at
a keyboard-style cadence (a ~300ms initial delay, then every 50ms) for as long
as the button stays pressed with the pointer over it; sliding off pauses the
repeat and pops the button back out, sliding back resumes it. (#41)EventCtx::request_tickasks the runtime to deliver anotherEvent::Tick
without any ancestor having to forward the request — the push counterpart to
Widget::wants_ticks. Likerequest_paint, it rides the sharedEventCtx
straight back to the runtime, so a widget buried under custom wrapper widgets
can drive a transient animation on its own. It is one-shot: a widget that
needs a stream re-requests on each tick. The scrollbar's hold-to-repeat uses
it, which is why it works even inside a wrapper that doesn't forward
wants_ticks(such as thefilerexample'sFileBrowser). (#41)FocusLabelis a caption that carries a keyboard mnemonic and moves focus to
the field beside it. Mark the accelerator with&exactly like a menu label
("Last &name:"underlines the n and binds Alt+N); pressing it
transfers focus to the next focusable widget added to the same parent — the
classic "buddy label" convention. The accelerator reaches the label even while
a sibling holds focus, via a newEventCtx::request_focus_nextrequest that
Container,Column, andRowresolve. See the newfocus_formexample.
(#39)MockBackend::render_framednow paints the window background pattern behind
the content for regular (resizable / fixed) windows, matching the live
backend's main surface; dialogs stay plain, as they do on screen. The pattern
defaults to the live default (asuperlightforward-diagonal hatch) and is
overridable with the newMockBackend::with_background_pattern. (#38)Listgained optional multi-selection, off by default so existing
single-selection lists are unchanged. Enable it withList::with_multi_select
/set_multi_select: Ctrl/Cmd+click toggles a row, Shift+click and Shift+Arrow
select a contiguous range, andselected_indices/set_selected_indicesread
and set the whole set. A plain press on an already-selected row defers
collapsing the selection until release, so a wrapper can drag the whole group
out — thepickerandfilerexamples now do. To carry the click modifiers,
Event::PointerDownandEvent::PointerUpnow include amodifiersfield.
(#37)- Widgets can request the mouse-pointer shape while handling a pointer event
viaEventCtx::set_cursor, choosing from the newCursorenum (arrow, hand,
I-beam, resize handles, …). The runtime applies it after each move on both
backends (wp_cursor_shapeon Wayland,CursorIconon X11/Windows/macOS) and
falls back to the arrow when no widget asks.TextInput/TextEditorshow
the I-beam over their text; every other widget keeps the default arrow. (#42) WindowConfig::min_sizesets the smallest inner size a resizable window may
be dragged to (in logical pixels). The window manager enforces the bound, so
layouts never see sizes below it. (#36)
Fixed
- On Wayland, the mouse pointer could keep a stale shape when entering a window
or surface. Wayland leaves the pointer image undefined onwl_pointer.enter
and makes the client set it, but the runtime deduplicated against the last
shape it had shown and so skipped re-establishing the arrow on entry. It now
forces the cursor shape on every enter (plain motion still dedups). (#42) - On X11, dragging a
ScrollBar/Sliderthumb (or any captured press) no
longer stops the moment the pointer leaves the window. winit reports the
cursor crossing the window edge as aCursorLefteven while X11's implicit
pointer grab keeps motion flowing during a held button, so the runtime took it
for a real leave and ended the drag. It now ignores that leave while a button
is held and a widget is capturing the pointer, so the drag keeps tracking
up/down motion until release — matching the Wayland backend, whose compositor
sends no leave during its implicit grab. (#40)
v0.3.0
Fixed
- The
filerexample no longer confuses scrolling with dragging a file out.
The drag-out gesture armed on a press anywhere inside the list bounds — which
include the scrollbar pinned to the right edge — so grabbing the thumb both
scrolled and armed a drag, and the drag won on the next move (yanking a file
out instead of scrolling). It now yields the scrollbar strip via the new
List::scrollbar_hit. Relatedly,ScrollBarandSlidernow end an
in-progress thumb drag onPointerLeave: with no OS pointer grab, a drag
interrupted by the pointer leaving the window (as an outbound drag-and-drop
does by revoking pointer focus) left a stale drag flag set, so the thumb
chased the cursor when it returned. (#35) TextEditoris no longer pathologically slow to repaint with large or
long-lined documents. Each frame rebuilt every visible row's caret-offset
table by re-measuring every prefix of the line (O(n²)) and re-rasterized every
glyph from scratch — including ones scrolled off the right edge — and the
runtime repaints the whole tree on every scroll notch and resize step. The
font now caches rasterized glyphs (in a bounded LRU) and per-glyph advances,
the caret table is built in one O(n) pass over those advances, and
Font::draw_physstops at the clip's right edge. Output is snapshot-identical;
the worst case is ~100× faster. (#34)include_svg!now maps every contour through itsabs_transform(the full
ancestor chain, viewBox→viewport origin offset and scale included), while still
framing the baked image by the SVG's declared viewport (the box resvg renders
into). This fixes SVGs that previously baked mis-scaled or off-frame — a viewBox
with a non-zero origin or an<svg>whose width/height differ from the viewBox
— without disturbing artwork that is deliberately padded inside its viewBox
(the scrollbar, dropdown, dialog, and checkbox marks). (#27, #31)- Firing a menu item by its keyboard mnemonic no longer leaks the letter into
whatever the item opens. Picking File → Open with Alt+F, O previously typed an
"o" into the dialog's freshly focused File name field; the menu now swallows
the keystroke through its release. (#30)
Added
FileDialog: a modern, single-pane Open / Save file picker (built onModal)
with the current path along the top, one combined list of folders and files,
a "File name" field, and a "File types" filter dropdown — the flat layout
modern KDE / Windows pickers use. Section labels carry Alt+L / Alt+N / Alt+T
accelerators. Glob-basedFileFilters drive the filter. Thenotepadexample
now uses it for File → Open and File → Save As. (#26)- Window-chrome screenshots:
MockBackend::render_framedwraps a rendered
client area in Canoe's default desktop style — a teal background, a soft drop
shadow, a navy active title bar, and a window frame. Choose the frame via
WindowChrome::resizable/fixed/dialog(WindowFrame), which mirror
Canoe's three window paints and differ in their window controls and border;
with_desktop_background/with_margintweak the backdrop. Windows are
always drawn active. See the newchromeexample. (#33) include_svg!now honorsclip-path: clip regions are intersected with the
drawn geometry at build time (viai_overlay), so clipped artwork bakes
correctly instead of being dropped.i_overlayis a compile-time-only
dependency ofsaudade-macrosand never reaches a shipped binary. (#27)include_svg!takes an optionalcropargument —
include_svg!("logo.svg", crop)— that frames the baked image by the tight
bounding box of the drawn geometry instead of the SVG's declared viewport,
dropping any padding so the mark fills its target rect. The default is still
viewport framing (matching resvg). (#31)include_svg!now approximates linear and radial gradient paint instead of
dropping it: each gradient bakes into a stack of flat-color bands (strips for
linear, nested disks for radial) clipped to the painted shape. Gradient fills
and strokes are no longer reported as unsupported. (#27)- File drag-and-drop: drop files from the OS onto a window. New
Event::DragEnter
/DragMove/DragLeave/Dropevents carry aDragDataof file paths,
and a drop target opts in by callingEventCtx::accept_drop()while handling
DragEnter/DragMove. Works on macOS, Windows, X11, and Wayland. See the
dndexample. (#23) - Dragging files out of a window (drag source), Wayland only:
EventCtx::start_drag()begins an OStext/uri-listdrag from a widget's
press-and-drag gesture, with an icon that follows the cursor and shows a green
checkmark over a target that accepts the drop or a red cross elsewhere. The
winit backends (macOS, Windows, X11) expose no API to initiate a drag, so it
is a no-op there. See thefilerexample. (#23) Dropdownpopups now scroll: a list longer than 12 rows caps the popup height
and grows a vertical scrollbar — mouse wheel, draggable thumb, Page Up/Down,
and scroll-the-selection-into-view all work — so a long list (e.g. the full
set of keyboard layouts) stays usable instead of opening a popup taller than
the screen. (#28)ScrollBar::end_drag()to abandon an in-progress thumb drag, for hosts that
can be torn down mid-drag (such as a dropdown popup that closes on focus
loss). (#28)ListItem::with_svg_icon: list rows can now show a compile-time-baked
SvgImage(frominclude_svg!), drawn crisply at any DPI, alongside the
existing rasterListIcon. (#32)EventCtx::swallow_key_until_release(): a handler that fully acts on a key
press can ask the runtime to discard the rest of it — the trailingChar,
autorepeat, and the release. (#30)Painter::light_button()(a lighter chrome frame: square outline, single
top/left highlight, 2px bottom/right shadow) andPainter::fill_checker()(a
two-tone DPI-aware checkerboard fill). (#29)
Changed
- The folder / file / up-arrow icons in the file dialog and the
filerexample
are now real SVG assets (assets/icons/*.svg) baked viainclude_svg!and
shared between the two, instead of hand-coded pixel buffers. (#32) ScrollBarchrome now matches Win 3.1 more closely: the arrow buttons and
thumb use the lighterlight_buttonframe (square outline, one highlight line
instead of two), the track gains a thin black outline that collapses into the
button/thumb frames where they meet, the arrow glyphs sit centered on the
button face with the classic margin instead of filling it edge to edge, and
the empty track shows the classic black-on-gray "newsprint"
checkerboard instead of a flat gray fill. (#29)- Adjacent
ScrollBaroutlines no longer double up into a 2px band where they
meet — each shared edge collapses to a single 1px line: a thumb slid flush
against an arrow button shares that button's edge, and a scrollbar embedded in
aListorTextEditorshares its outer edge with the field's border. (#29)
saudade-macros-v0.2.0
chore: Release saudade-macros version 0.2.0
v0.2.0
Added
- Compile-time SVG support: the
include_svg!macro bakes an SVG into
flattened polygons at build time and expands to aconst SvgImage, so no SVG
parser is linked into the binary. The parsing weight (usvg + kurbo) is
confined to the newsaudade-macrosproc-macro crate and runs only at compile
time. (#22) Modal::on_cancel()to run a handler when a modal is dismissed;Appnow
also invokeson_cancel()for top-level windows. (#18, #19)Container::layout()for custom child layout. (#17)- Nested popups — a popup opened from within another popup. (#11)
- Painting at physical size, with a dedicated code path for crisper rendering at
1.25× scale. (#13) - The system scale factor is now readable on Wayland. (#16)
Changed
Fixed
saudade-macros-v0.1.0
- Compile-time SVG support (
include_svg!) by @roblillack in #22
v0.1.0
Initial release