The print dialog in the CHANGELOG and the widget catalogue
CI / build + test (push) Canceled after 0s
CI / cargo audit (push) Canceled after 0s

The commit that added `ltk::print` left both out. `CHANGELOG.md` gains its entry under Added, after `ltk::dirs`: what the dialog offers, how the app answers its requests for a preview, CUPS loaded at first use, the printers, jobs and default printer for a settings page, the print portal for an app in a Flatpak with `ltk::print::gtk` for a portal backend, zbus in the `wayland` feature, and only saving as PDF on Windows, Android and in a browser.
`docs/widgets.md` describes `print_dialog` among the pickers, after `file_chooser`, and links it from the table of contents: a component the app holds as it holds the file chooser, the preview the app renders on request, the job it delivers with `ltk::print::send`, and the builders that choose where it starts. Its example opens the dialog, answers its events and sends the PDF to a printer, and is compiled by `make doctest-md`.
This commit is contained in:
2026-11-01 22:53:46 +01:00
parent 45b8ddd483
commit 058a65546d
2 changed files with 48 additions and 1 deletions
+1
View File
@@ -8,6 +8,7 @@ All notable changes to `ltk` are documented here. The format is based on [Keep a
- **A Windows backend: the `windows` feature** — `ltk::run` in a Win32 window on Windows 10 1703 or later, with Windows's own frame, drawn through ANGLE's EGL and OpenGL ES when `libEGL.dll` and `libGLESv2.dll` sit beside the executable and in software through GDI otherwise. It sizes the window by `App::window_size_hint` at the display's DPI and follows per-monitor DPI changes at fractional scales, the accessibility text size, the mouse, wheel, touch through `WM_POINTER`, keys with Windows's repeat, dead keys and AltGr, the system clipboard, the cursor's shape, full screen, and moves and resizes begun from the app's own chrome, and keeps drawing while the window is moved or resized. Themes, fonts, keyboard layouts and MIME globs come from `ltk\` beside the executable, the default resource bundle on Windows (`ltk::resource_dir`), and the user's home and data folders from `USERPROFILE` and `APPDATA` (`dirs.rs`); the font fallback chain is Windows's own, and the file chooser offers each drive's root. The Windows backend runs the runtime's timers and channels on the queue the web backend already used, since a Win32 message loop cannot wait on calloop; both now share it as `cfg( queued_sources )` and `AppData::dispatch_sources`. `Canvas::present_to` presents a GPU frame stretched over a window a fractional scale left a pixel off it. `App::save_state` is saved into `%LOCALAPPDATA%\<app_id>\` every 30 seconds, on exit and when the user's session ends, and restored as on Wayland: the part of the Wayland session runtime that does not speak the protocol, deciding why the app starts and handing it its bytes, is now `session_state::bootstrap`, which both backends call. `RunError::Window` reports a window Windows refused. `make windows-deps` and `make windows-check` typecheck the backend from Debian, compiling std from `rust-src`. `docs/windows.md` describes building, what ships beside the executable and what is not done yet: overlays and subsurfaces, the input method's preedit and accessibility.
- **`ltk::dirs`** — the user's folders, `home`, `data_home`, `config_home`, `cache_home` and `state_home`: XDG's on Linux, with their usual fallbacks under the home folder, and on Windows `USERPROFILE`, `APPDATA` for data and settings and `LOCALAPPDATA` for the cache and state, an `XDG_*` variable that is set winning everywhere. An app finds its files there instead of reading `HOME` and the XDG variables itself, which on Windows are unset; ltk's own lookups of themes, keyboard layouts, MIME globs, the file chooser's folders and the session state go through it.
- **A print dialog: `ltk::print`** — `PrintDialog` asks where and how to print, built as `FileChooser` is: the app holds it, puts its `view()` in its own and hands its `PrintDialogMsg`s to `update()`, which returns a `PrintEvent`. It offers the printers CUPS knows, with their state, or saving as PDF, the copies, all pages or a range such as `1-3, 5, 8-` (`parse_pages`, `format_pages`), the paper, the orientation, the margins, the scale, two-sided printing, colour and background graphics, which `PrintOptions` holds between dialogs; a card on a desktop, a whole page with `compact( true )`. With `preview( true )` it asks the app for the pages with `PrintEvent::Preview` whenever the layout changes and shows the `Preview` handed back through `PrintDialogMsg::previewed`, one page at a time, waiting for one render before asking for the next. CUPS is loaded with `dlopen` at first use, so ltk does not link against it and a system without it only loses its printers; `ltk::print::send` prints a PDF, and `list_printers`, `jobs`, `cancel_job` and `set_default` serve a settings page, on a thread and through the app's sender, or blocking from `ltk::print::blocking`. An app in a Flatpak shows the print portal's dialog instead, and `send` hands the PDF to the portal; `ltk::print::gtk` translates the options to and from GTK's print settings, as a portal backend needs. The `wayland` feature brings zbus 4 for the portal, the version accesskit_unix already uses. On Windows, Android and in a browser only saving as PDF is offered. Its strings are in ltk's seven locales.
- **`App::window_title`** — the main window's title, read when the window is made, which the title bar, the task switcher and the dock show; by default the `app_id`. A `ShellMode::Window` app was titled "ltk" on Wayland, whatever the app, with no way to name it but the deprecated `window_config`; in a browser its title was its `app_id`, and is now this.
- **`Text::fit` and `Text::valign`** — `fit()` draws a text at the largest size up to its own at which it fits the rect the layout gives it: on one line within the rect's width and height, or, with `wrap`, all its lines within its height. A text whose length varies, such as a number, a total or a title, in a box whose size the window sets, so neither overflows the box nor needs its size worked out from the window and the length of its content. Its preferred size is still the one at its own size. `valign( VAlign )` places the lines at the top (the default), the centre or the bottom of a rect taller than they are, as a fitted text's may be.
- **A text field as an editor: `TextEdit::highlights`, `TextEdit::tab_text` and `App::take_text_command`** — `highlights( Arc<Vec<TextHighlight>> )` draws runs of a multiline value, byte ranges in order, in colours of their own, as a syntax highlighter marks keywords, strings and comments; what no run covers keeps the field's colour, and a run that does not fall on character boundaries is left out. `tab_text( text )` has Tab type `text`, such as four spaces, while the field has the focus, instead of moving the focus on; Shift+Tab still moves it back, and Ctrl+Tab still reaches the app. `App::take_text_command` returns once a `WidgetId` and a `TextCommand` for that field, `Copy`, `Cut`, `Paste`, `SelectAll` or `Select { start, end }`, which the runtime carries out after the next frame, focusing the field first: a field's cursor and selection are the runtime's, so an app's Edit menu or its search had no way to copy what was selected or to select what it found. `Select` takes its offsets to character boundaries within the value and scrolls a multiline field to them. `borderless` now also leaves out a multiline field's background and border, as an editor filling its window wants. Tests cover Tab with and without `tab_text`, selecting and copying, offsets inside a character, cutting, selecting all, and a run inside a character.
+47 -1
View File
@@ -26,7 +26,7 @@ patterns built from these widgets, see [`docs/cookbook.md`](./cookbook.md).
- [Overlays and feedback](#overlays-and-feedback)
- [`spinner`](#spinner) · [`toast`](#toast) · [`tooltip`](#tooltip) · [`anchored`](#anchored) · [`combo`](#combo) · [`tabs`](#tabs) · [`notebook`](#notebook) · [`tab_strip`](#tab_strip) · [`dialog`](#dialog)
- [Pickers](#pickers)
- [`date_picker`](#date_picker) · [`time_picker`](#time_picker) · [`color_picker`](#color_picker) · [`file_chooser`](#file_chooser)
- [`date_picker`](#date_picker) · [`time_picker`](#time_picker) · [`color_picker`](#color_picker) · [`file_chooser`](#file_chooser) · [`print_dialog`](#print_dialog)
- [Layouts](#layouts)
- [`column`](#column) · [`row`](#row) · [`stack`](#stack) · [`grid`](#grid) · [`spacer`](#spacer)
@@ -1061,6 +1061,52 @@ fn update( &mut self, msg: Msg )
---
### `print_dialog`
Where and how to print, with a preview of the pages. Like the file chooser it is a component the app holds: `PrintDialog::new` makes it, its `view()` goes in the app's view, and its `PrintDialogMsg`s, which `wrap` makes the app's own, go to `update()`, which returns a `PrintEvent`. It lists the printers CUPS knows on its own through the app's `ChannelSender`, with "Save as PDF" after them, and offers the copies, the pages, the paper, the orientation, the margins, the scale, two-sided printing, colour and background graphics. The app renders the document itself: with `preview( true )` the dialog sends `PrintEvent::Preview( options )` whenever the layout changes, and the app answers with `PrintDialogMsg::previewed`, a `Preview` of pages rendered about `PREVIEW_WIDTH` pixels wide; `PrintEvent::Print( job )` says where and how once the user accepts, and `ltk::print::send` delivers the PDF to a printer or, in a Flatpak, to the print portal, whose own dialog the user saw instead. `options` starts from options kept from an earlier job, `printer` or `save_as_pdf` choose where it starts, `compact( true )` makes it a whole page for a phone, and `take_focus_request` gives the field to focus.
**When**: any app that prints, instead of a dialog of its own.
```rust,no_run
# use ltk::{ ChannelSender, Element };
# use ltk::print::{ Destination, PrintDialog, PrintDialogMsg, PrintEvent, PrintOptions };
# #[ derive( Clone ) ] enum Msg { Print, Dialog( PrintDialogMsg ), Sent( Result<(), String> ) }
# struct App { print: Option<PrintDialog<Msg>>, sender: ChannelSender<Msg> }
# impl App {
# fn render( &self, _options: &PrintOptions ) -> Vec<u8> { Vec::new() }
fn update( &mut self, msg: Msg )
{
match msg
{
Msg::Print => self.print = Some( PrintDialog::new( &self.sender, Msg::Dialog, "Report" ).preview( true ) ),
Msg::Dialog( msg ) =>
{
let Some( dialog ) = &mut self.print else { return };
match dialog.update( msg )
{
Some( PrintEvent::Preview( _options ) ) => { /* render the pages, then send Msg::Dialog( PrintDialogMsg::previewed( .. ) ) */ }
Some( PrintEvent::Print( job ) ) =>
{
self.print = None;
if job.destination == Destination::Pdf { return; /* ask for a file with a FileChooser */ }
let pdf = self.render( &job.options );
ltk::print::send( &job.destination, &job.title, &job.options, pdf, &self.sender, Msg::Sent );
}
Some( PrintEvent::Cancelled ) => self.print = None,
None => {}
}
}
Msg::Sent( _result ) => {}
}
}
# fn view( &self ) -> Element<Msg> { self.print.as_ref().map( PrintDialog::view ).unwrap() }
# }
```
**See also**: [`ltk::print`](../src/print/mod.rs) for the printers, their jobs and the default printer, as a settings page shows them, and `ltk::print::gtk` for the portal's print settings.
---
## Layouts
### `column`