Skip to content

Releases: roblillack/saudade

v0.5.0

10 Jun 16:40

Choose a tag to compare

Added

  • Menu items can be disabled. MenuItem::with_enabled attaches 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_checked attaches 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 one Checkbox uses, 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 new Accel type (or its conventional string form: "Ctrl+R",
    "Ctrl+Shift+Enter"), and pressing a matching chord while the bar is closed
    fires the item directly. Accel names its modifiers as platform-independent
    roles (AccelMods): Accel::primary('r') means ⌘R on macOS and Ctrl+R
    everywhere else, resolved through a ModifierScheme both when matching
    input and when rendering the right-aligned hint. The scheme defaults to the
    build platform's; MenuBar::with_scheme pins 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's accel field is
    now Option<Accel> instead of Option<String>with_accel("Ctrl+R")
    call sites keep compiling via From<&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) and FontStyle (Regular / Bold / Italic / BoldItalic). Draw
    with Painter::text_styled(.., family, style) and measure with
    Painter::measure_text_styled(.., family, style); the existing text /
    measure_text stay sans-serif regular, so nothing already on screen moves. (#44)
  • Font::load_sans (renamed from load_system), Font::load_serif, and
    Font::load_monospace load 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 from from_bytes) gains with_bold_bytes, with_italic_bytes, and
    with_bold_italic_bytes builders to attach the emphasis faces from in-memory
    buffers, and MockBackend gains with_serif_font alongside with_sans_font
    (renamed from with_font) / with_mono_font.
  • Painter::blit_argb blits 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-pixel pixel() calls is the bottleneck. Each source pixel still
    snaps to the same physical block pixel() 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::new and Painter::with_popup_anchor now take a single
    FontSet { sans, serif, mono } in place of the two separate Option<&Font>
    arguments. A backend builds one FontSet from the fonts it owns; offscreen
    painters with no text pass FontSet::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_systemFont::load_sans,
    Font::from_bytesFont::from_sans_bytes, and MockBackend::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, and mono_cumulative_widths are removed in favor of the
    general family+style API: draw with text_styled(.., FontFamily::Mono, FontStyle::Regular), measure with measure_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 a FontStyle argument. (#44)

Fixed

  • TextEditor and List no longer jump their scroll position back to the
    caret / selected row when the window merely gains or loses focus. Their
    layout re-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 a configure (hence a relayout) on every
    activation change — so a wheel-scrolled view snapped back the instant focus
    changed (visible in the notepad example as the caret jumping to the first
    line). layout now 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

08 Jun 19:40

Choose a tag to compare

Added

  • ScrollBar arrow 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_tick asks the runtime to deliver another Event::Tick
    without any ancestor having to forward the request — the push counterpart to
    Widget::wants_ticks. Like request_paint, it rides the shared EventCtx
    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 the filer example's FileBrowser). (#41)
  • FocusLabel is 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 new EventCtx::request_focus_next request that
    Container, Column, and Row resolve. See the new focus_form example.
    (#39)
  • MockBackend::render_framed now 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 (a superlight forward-diagonal hatch) and is
    overridable with the new MockBackend::with_background_pattern. (#38)
  • List gained optional multi-selection, off by default so existing
    single-selection lists are unchanged. Enable it with List::with_multi_select
    / set_multi_select: Ctrl/Cmd+click toggles a row, Shift+click and Shift+Arrow
    select a contiguous range, and selected_indices / set_selected_indices read
    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 — the picker and filer examples now do. To carry the click modifiers,
    Event::PointerDown and Event::PointerUp now include a modifiers field.
    (#37)
  • Widgets can request the mouse-pointer shape while handling a pointer event
    via EventCtx::set_cursor, choosing from the new Cursor enum (arrow, hand,
    I-beam, resize handles, …). The runtime applies it after each move on both
    backends (wp_cursor_shape on Wayland, CursorIcon on X11/Windows/macOS) and
    falls back to the arrow when no widget asks. TextInput / TextEditor show
    the I-beam over their text; every other widget keeps the default arrow. (#42)
  • WindowConfig::min_size sets 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 on wl_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 / Slider thumb (or any captured press) no
    longer stops the moment the pointer leaves the window. winit reports the
    cursor crossing the window edge as a CursorLeft even 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

07 Jun 12:58

Choose a tag to compare

Fixed

  • The filer example 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, ScrollBar and Slider now end an
    in-progress thumb drag on PointerLeave: 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)
  • TextEditor is 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_phys stops at the clip's right edge. Output is snapshot-identical;
    the worst case is ~100× faster. (#34)
  • include_svg! now maps every contour through its abs_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 on Modal)
    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-based FileFilters drive the filter. The notepad example
    now uses it for File → Open and File → Save As. (#26)
  • Window-chrome screenshots: MockBackend::render_framed wraps 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_margin tweak the backdrop. Windows are
    always drawn active. See the new chrome example. (#33)
  • include_svg! now honors clip-path: clip regions are intersected with the
    drawn geometry at build time (via i_overlay), so clipped artwork bakes
    correctly instead of being dropped. i_overlay is a compile-time-only
    dependency of saudade-macros and never reaches a shipped binary. (#27)
  • include_svg! takes an optional crop argument —
    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 / Drop events carry a DragData of file paths,
    and a drop target opts in by calling EventCtx::accept_drop() while handling
    DragEnter / DragMove. Works on macOS, Windows, X11, and Wayland. See the
    dnd example. (#23)
  • Dragging files out of a window (drag source), Wayland only:
    EventCtx::start_drag() begins an OS text/uri-list drag 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 the filer example. (#23)
  • Dropdown popups 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 (from include_svg!), drawn crisply at any DPI, alongside the
    existing raster ListIcon. (#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 trailing Char,
    autorepeat, and the release. (#30)
  • Painter::light_button() (a lighter chrome frame: square outline, single
    top/left highlight, 2px bottom/right shadow) and Painter::fill_checker() (a
    two-tone DPI-aware checkerboard fill). (#29)

Changed

  • The folder / file / up-arrow icons in the file dialog and the filer example
    are now real SVG assets (assets/icons/*.svg) baked via include_svg! and
    shared between the two, instead of hand-coded pixel buffers. (#32)
  • ScrollBar chrome now matches Win 3.1 more closely: the arrow buttons and
    thumb use the lighter light_button frame (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 ScrollBar outlines 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
    a List or TextEditor shares its outer edge with the field's border. (#29)

saudade-macros-v0.2.0

07 Jun 12:57

Choose a tag to compare

chore: Release saudade-macros version 0.2.0

v0.2.0

06 Jun 08:16

Choose a tag to compare

Added

  • Compile-time SVG support: the include_svg! macro bakes an SVG into
    flattened polygons at build time and expands to a const SvgImage, so no SVG
    parser is linked into the binary. The parsing weight (usvg + kurbo) is
    confined to the new saudade-macros proc-macro crate and runs only at compile
    time. (#22)
  • Modal::on_cancel() to run a handler when a modal is dismissed; App now
    also invokes on_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

  • Reworked the dialog architecture. (#21)
  • Refreshed checkbox and dropdown styling. (#24, #25)

Fixed

  • Buttons now activate on key release rather than on key press. (#14)
  • Corrected button autorepeat behavior. (#20)
  • FreeBSD: load fonts from /usr/local/share/fonts. (#12)

saudade-macros-v0.1.0

06 Jun 08:15

Choose a tag to compare

v0.1.0

06 Jun 08:14

Choose a tag to compare

Initial release