ltk: ext-session-lock-v1 client surface mode, plus a read-only mode for text fields
Add a third Wayland surface type to the runtime so an ltk `App` can be a screen locker, alongside the existing xdg-shell window and wlr-layer-shell surfaces. A new `ShellMode::SessionLock` makes `run()` bind `ext_session_lock_manager_v1` and request the lock at startup; the lock surface itself is created in the new `SessionLockHandler::locked` callback (one surface on the first advertised output) and replaces the `SurfaceKind::PendingLock` placeholder the main surface holds until the compositor grants the lock. The `configure` event routes through the same `on_configure` path as layer and xdg surfaces, so sizing and rendering are unchanged, and `finished` (the compositor denied or ended the lock) tears the loop down. The whole thing is additive and opt-in: the `Window` and `Layer` paths are untouched and nothing enters lock mode unless an `App` returns `ShellMode::SessionLock`, so existing apps are unaffected — the only non-additive edits are the two exhaustive `match`es on `SurfaceKind` (`wl_surface` / `try_wl_surface`), which gain arms for the two new variants. Doing the locker as a first-class surface rather than compositing a static texture into an offscreen `UiSurface` is the whole point: the compositor gives the lock surface keyboard focus, so ltk's existing text-input, editing, focus and IME machinery works inside the lock exactly as on any other surface — cursor, click-to-focus, Tab, character input. A locker built on top of this is just a normal interactive ltk app that happens to be presented on the lock layer, with no special input plumbing on the compositor or the app side. `App::requested_exit()` is the new way an app asks the runtime to tear the surface down and leave the loop; it is polled after every batch of `update`s. It exists because of the one hard invariant of `ext-session-lock-v1`: a locker that disconnects without sending `unlock` leaves the compositor's outputs blanked forever — that is the protocol's deliberate anti-bypass guarantee. So when `requested_exit()` returns true and the surface is a session lock, the loop calls `session_lock.unlock()` and round-trips the connection before setting `exit_requested`, lifting the lock cleanly; for a `Window` or `Layer` surface there is no lock and it simply exits. The consequence for lock apps is that they must stop calling `process::exit` from the lock path and instead flip a flag they return from `requested_exit()`. `text_edit` gains a `read_only( bool )` builder. A read-only field still renders its box and value in the normal field style but takes no keyboard focus and accepts no input: `Element::is_focusable` and `Element::is_text_input` now return false for a read-only `TextEdit`, which keeps it out of the Tab cycle, off the keyboard-edit path, and stops the cursor from ever being drawn on it. The flag is carried through `map_msg` so it survives `Element::map`. This is for presenting a known, non-editable value in the same visual idiom as the editable fields beside it — for example the already-known user shown on a session lock, where letting that field take focus or blink a cursor would be wrong. The `shell_mode()` doc comment and the README now list the `SessionLock` surface type and point at `requested_exit()` for the unlock. Two warnings are cleared along the way: the runtime no longer stores the `SessionLockState` after requesting the lock — it has no `Drop`, so the manager object outlives the dropped handle inside the connection and the lock lifecycle runs entirely off the returned `SessionLock`, which removes a never-read field — and a pre-existing rustdoc `private_intra_doc_links` warning in `list_item` (a public doc comment linking to the private `theme::ICON_SIZE`) is downgraded to plain code formatting.
This commit is contained in:
@@ -66,7 +66,7 @@ model is "public Wayland toolkit with production consumers" rather than
|
|||||||
- low idle wakeups and event-driven redraws
|
- low idle wakeups and event-driven redraws
|
||||||
- partial redraws and damage tracking
|
- partial redraws and damage tracking
|
||||||
- a simple declarative tree instead of retained widgets
|
- a simple declarative tree instead of retained widgets
|
||||||
- direct support for normal windows and layer-shell surfaces
|
- direct support for normal windows, layer-shell, and ext-session-lock surfaces
|
||||||
- a runtime-free core (`ltk::core::UiSurface`) for embedding layout and drawing
|
- a runtime-free core (`ltk::core::UiSurface`) for embedding layout and drawing
|
||||||
without `ltk::run()`
|
without `ltk::run()`
|
||||||
|
|
||||||
@@ -230,6 +230,11 @@ Switch to layer-shell only when you are building shell surfaces such as:
|
|||||||
- greeters
|
- greeters
|
||||||
- lock screens
|
- lock screens
|
||||||
|
|
||||||
|
For a screen locker, use `ShellMode::SessionLock` instead of layer-shell: it
|
||||||
|
presents an `ext-session-lock-v1` surface that the compositor keeps on top of
|
||||||
|
everything until the app returns `true` from `requested_exit()`, which makes
|
||||||
|
the runtime call `unlock` and lift the lock.
|
||||||
|
|
||||||
## Performance Notes
|
## Performance Notes
|
||||||
|
|
||||||
`ltk` is designed to sleep when idle and redraw only on real work.
|
`ltk` is designed to sleep when idle and redraw only on real work.
|
||||||
|
|||||||
14
src/app.rs
14
src/app.rs
@@ -33,6 +33,11 @@ pub enum ShellMode
|
|||||||
/// Layer-shell surface at the specified layer.
|
/// Layer-shell surface at the specified layer.
|
||||||
/// Used for shell components like panels, backgrounds, overlays.
|
/// Used for shell components like panels, backgrounds, overlays.
|
||||||
Layer( Layer ),
|
Layer( Layer ),
|
||||||
|
|
||||||
|
/// `ext-session-lock-v1` lock surface. The compositor blanks the outputs
|
||||||
|
/// and shows only this surface until the app requests exit (see
|
||||||
|
/// [`App::requested_exit`]). Used by the screen locker.
|
||||||
|
SessionLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layer-shell layer position.
|
/// Layer-shell layer position.
|
||||||
@@ -641,11 +646,20 @@ pub trait App: 'static
|
|||||||
///
|
///
|
||||||
/// - [`ShellMode::Window`]: Normal application window (xdg-shell). **Default.**
|
/// - [`ShellMode::Window`]: Normal application window (xdg-shell). **Default.**
|
||||||
/// - [`ShellMode::Layer`]: System component at a specific layer (layer-shell).
|
/// - [`ShellMode::Layer`]: System component at a specific layer (layer-shell).
|
||||||
|
/// - [`ShellMode::SessionLock`]: `ext-session-lock-v1` lock surface (screen locker).
|
||||||
///
|
///
|
||||||
/// For regular applications, use the default `Window` mode.
|
/// For regular applications, use the default `Window` mode.
|
||||||
/// For shell components (panels, backgrounds, overlays), use `Layer`.
|
/// For shell components (panels, backgrounds, overlays), use `Layer`.
|
||||||
|
/// For a screen locker, use `SessionLock` and request the unlock by
|
||||||
|
/// returning `true` from [`requested_exit`](Self::requested_exit).
|
||||||
fn shell_mode( &self ) -> ShellMode { ShellMode::Window }
|
fn shell_mode( &self ) -> ShellMode { ShellMode::Window }
|
||||||
|
|
||||||
|
/// Return `true` to tear the surface down and exit the event loop. For a
|
||||||
|
/// [`ShellMode::SessionLock`] surface the runtime calls `unlock` first, so
|
||||||
|
/// the compositor lifts the lock instead of leaving the outputs blanked.
|
||||||
|
/// Polled after every batch of `update`s.
|
||||||
|
fn requested_exit( &self ) -> bool { false }
|
||||||
|
|
||||||
/// Suggest an initial size for an xdg-shell window in logical pixels.
|
/// Suggest an initial size for an xdg-shell window in logical pixels.
|
||||||
///
|
///
|
||||||
/// Returning `Some(( w, h ))` makes ltk call both
|
/// Returning `Some(( w, h ))` makes ltk call both
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use smithay_client_toolkit::
|
|||||||
seat::SeatState,
|
seat::SeatState,
|
||||||
shell::{ wlr_layer::LayerShell, xdg::XdgShell },
|
shell::{ wlr_layer::LayerShell, xdg::XdgShell },
|
||||||
shm::Shm,
|
shm::Shm,
|
||||||
|
session_lock::SessionLock,
|
||||||
};
|
};
|
||||||
use smithay_client_toolkit::reexports::client::
|
use smithay_client_toolkit::reexports::client::
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,7 @@ pub struct AppData<A: App>
|
|||||||
pub output_state: OutputState,
|
pub output_state: OutputState,
|
||||||
pub compositor_state: CompositorState,
|
pub compositor_state: CompositorState,
|
||||||
pub shm: Shm,
|
pub shm: Shm,
|
||||||
|
pub session_lock: Option<SessionLock>,
|
||||||
/// Process-wide EGL context (display + GLES context). `None` when EGL
|
/// Process-wide EGL context (display + GLES context). `None` when EGL
|
||||||
/// failed to initialise or `LTK_FORCE_SOFTWARE=1` — every surface then
|
/// failed to initialise or `LTK_FORCE_SOFTWARE=1` — every surface then
|
||||||
/// falls back to the SHM path.
|
/// falls back to the SHM path.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use smithay_client_toolkit::
|
|||||||
delegate_compositor, delegate_foreign_toplevel_list, delegate_layer,
|
delegate_compositor, delegate_foreign_toplevel_list, delegate_layer,
|
||||||
delegate_output, delegate_registry, delegate_seat, delegate_keyboard,
|
delegate_output, delegate_registry, delegate_seat, delegate_keyboard,
|
||||||
delegate_pointer, delegate_touch, delegate_shm, delegate_xdg_popup,
|
delegate_pointer, delegate_touch, delegate_shm, delegate_xdg_popup,
|
||||||
delegate_xdg_shell, delegate_xdg_window,
|
delegate_xdg_shell, delegate_xdg_window, delegate_session_lock,
|
||||||
foreign_toplevel_list::{ ForeignToplevelList, ForeignToplevelListHandler },
|
foreign_toplevel_list::{ ForeignToplevelList, ForeignToplevelListHandler },
|
||||||
output::{ OutputHandler, OutputState },
|
output::{ OutputHandler, OutputState },
|
||||||
registry::{ ProvidesRegistryState, RegistryState },
|
registry::{ ProvidesRegistryState, RegistryState },
|
||||||
@@ -22,6 +22,7 @@ use smithay_client_toolkit::
|
|||||||
xdg::window::{ Window, WindowConfigure, WindowHandler },
|
xdg::window::{ Window, WindowConfigure, WindowHandler },
|
||||||
},
|
},
|
||||||
shm::{ Shm, ShmHandler },
|
shm::{ Shm, ShmHandler },
|
||||||
|
session_lock::{ SessionLock, SessionLockHandler, SessionLockSurface, SessionLockSurfaceConfigure },
|
||||||
};
|
};
|
||||||
use smithay_client_toolkit::reexports::protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1;
|
use smithay_client_toolkit::reexports::protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1;
|
||||||
use smithay_client_toolkit::reexports::client::
|
use smithay_client_toolkit::reexports::client::
|
||||||
@@ -396,6 +397,39 @@ impl<A: App> PopupHandler for AppData<A>
|
|||||||
|
|
||||||
// --- Delegate macros ---
|
// --- Delegate macros ---
|
||||||
|
|
||||||
|
impl<A: App> SessionLockHandler for AppData<A>
|
||||||
|
{
|
||||||
|
fn locked( &mut self, _conn: &Connection, qh: &QueueHandle<Self>, session_lock: SessionLock )
|
||||||
|
{
|
||||||
|
if let Some( output ) = self.output_state.outputs().next()
|
||||||
|
{
|
||||||
|
let surface = self.compositor_state.create_surface( qh );
|
||||||
|
let lock_surface = session_lock.create_lock_surface( surface, &output, qh );
|
||||||
|
self.main.surface = super::SurfaceKind::Lock( lock_surface );
|
||||||
|
}
|
||||||
|
self.session_lock = Some( session_lock );
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finished( &mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _session_lock: SessionLock )
|
||||||
|
{
|
||||||
|
// Compositor refused the lock or ended it; nothing left to show.
|
||||||
|
self.exit_requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_surface: SessionLockSurface,
|
||||||
|
configure: SessionLockSurfaceConfigure,
|
||||||
|
_serial: u32,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let ( w, h ) = configure.new_size;
|
||||||
|
self.on_configure( w.max( 1 ), h.max( 1 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate_compositor!( @<A: App> AppData<A> );
|
delegate_compositor!( @<A: App> AppData<A> );
|
||||||
delegate_output!( @<A: App> AppData<A> );
|
delegate_output!( @<A: App> AppData<A> );
|
||||||
delegate_shm!( @<A: App> AppData<A> );
|
delegate_shm!( @<A: App> AppData<A> );
|
||||||
@@ -404,6 +438,7 @@ delegate_keyboard!( @<A: App> AppData<A> );
|
|||||||
delegate_pointer!( @<A: App> AppData<A> );
|
delegate_pointer!( @<A: App> AppData<A> );
|
||||||
delegate_touch!( @<A: App> AppData<A> );
|
delegate_touch!( @<A: App> AppData<A> );
|
||||||
delegate_layer!( @<A: App> AppData<A> );
|
delegate_layer!( @<A: App> AppData<A> );
|
||||||
|
delegate_session_lock!( @<A: App> AppData<A> );
|
||||||
delegate_xdg_shell!( @<A: App> AppData<A> );
|
delegate_xdg_shell!( @<A: App> AppData<A> );
|
||||||
delegate_xdg_window!( @<A: App> AppData<A> );
|
delegate_xdg_window!( @<A: App> AppData<A> );
|
||||||
delegate_xdg_popup!( @<A: App> AppData<A> );
|
delegate_xdg_popup!( @<A: App> AppData<A> );
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ pub( crate ) fn try_run<A: App>( app: A ) -> Result<(), RunError>
|
|||||||
use crate::app::ShellMode;
|
use crate::app::ShellMode;
|
||||||
match app.shell_mode()
|
match app.shell_mode()
|
||||||
{
|
{
|
||||||
|
ShellMode::SessionLock => {
|
||||||
|
// Lock surface is created in SessionLockHandler::locked once the
|
||||||
|
// compositor grants the lock; until then a placeholder.
|
||||||
|
( SurfaceKind::PendingLock, None )
|
||||||
|
}
|
||||||
ShellMode::Window => {
|
ShellMode::Window => {
|
||||||
let xdg = bind_xdg( &globals, &qh )?;
|
let xdg = bind_xdg( &globals, &qh )?;
|
||||||
let surface = compositor.create_surface( &qh );
|
let surface = compositor.create_surface( &qh );
|
||||||
@@ -183,6 +188,20 @@ pub( crate ) fn try_run<A: App>( app: A ) -> Result<(), RunError>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bind the session-lock manager and request the lock. We don't keep the
|
||||||
|
// `SessionLockState` (the manager) around: it has no `Drop`, so the
|
||||||
|
// manager object persists in the connection, and the lock lifecycle runs
|
||||||
|
// entirely off the returned `SessionLock` + its surfaces.
|
||||||
|
let session_lock =
|
||||||
|
if force_window.is_none() && matches!( app.shell_mode(), crate::app::ShellMode::SessionLock )
|
||||||
|
{
|
||||||
|
smithay_client_toolkit::session_lock::SessionLockState::new( &globals, &qh )
|
||||||
|
.lock( &qh )
|
||||||
|
.ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let text_input_manager: Option<ZwpTextInputManagerV3> = globals.bind( &qh, 1..=1, () ).ok();
|
let text_input_manager: Option<ZwpTextInputManagerV3> = globals.bind( &qh, 1..=1, () ).ok();
|
||||||
// wl_data_device_manager: optional. Required for cross-application
|
// wl_data_device_manager: optional. Required for cross-application
|
||||||
// copy/paste; absence means the clipboard stays process-local.
|
// copy/paste; absence means the clipboard stays process-local.
|
||||||
@@ -243,6 +262,7 @@ pub( crate ) fn try_run<A: App>( app: A ) -> Result<(), RunError>
|
|||||||
output_state: OutputState::new( &globals, &qh ),
|
output_state: OutputState::new( &globals, &qh ),
|
||||||
compositor_state: compositor,
|
compositor_state: compositor,
|
||||||
shm,
|
shm,
|
||||||
|
session_lock,
|
||||||
egl_context,
|
egl_context,
|
||||||
xdg_shell,
|
xdg_shell,
|
||||||
layer_shell: layer_shell_opt,
|
layer_shell: layer_shell_opt,
|
||||||
@@ -459,6 +479,20 @@ pub( crate ) fn try_run<A: App>( app: A ) -> Result<(), RunError>
|
|||||||
let pf = data.pointer_focus;
|
let pf = data.pointer_focus;
|
||||||
data.dispatch_cursor_shape( pf );
|
data.dispatch_cursor_shape( pf );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App asked to exit (e.g. the lockscreen authenticated). For a
|
||||||
|
// session-lock surface, unlock first — exiting without unlocking
|
||||||
|
// leaves the compositor's lock in place by design.
|
||||||
|
if data.app.requested_exit()
|
||||||
|
{
|
||||||
|
if let Some( lock ) = data.session_lock.take()
|
||||||
|
{
|
||||||
|
lock.unlock();
|
||||||
|
let _ = conn.roundtrip();
|
||||||
|
}
|
||||||
|
data.exit_requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Seed `on_drag_move` with the long-press origin for any drag that
|
// Seed `on_drag_move` with the long-press origin for any drag that
|
||||||
// just started. Must run *after* `update()` so the app's drag state
|
// just started. Must run *after* `update()` so the app's drag state
|
||||||
// has already been set up by the paired long-press message — the
|
// has already been set up by the paired long-press message — the
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use smithay_client_toolkit::reexports::client::
|
|||||||
protocol::wl_surface::WlSurface,
|
protocol::wl_surface::WlSurface,
|
||||||
QueueHandle,
|
QueueHandle,
|
||||||
};
|
};
|
||||||
|
use smithay_client_toolkit::session_lock::SessionLockSurface;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -70,6 +71,11 @@ pub( crate ) enum SurfaceKind
|
|||||||
/// to an anchor rect specified at creation time and may flip it
|
/// to an anchor rect specified at creation time and may flip it
|
||||||
/// (drop-up vs drop-down) when constrained.
|
/// (drop-up vs drop-down) when constrained.
|
||||||
Popup( Popup ),
|
Popup( Popup ),
|
||||||
|
/// `ext-session-lock-v1` lock surface, created in `SessionLockHandler::locked`.
|
||||||
|
Lock( SessionLockSurface ),
|
||||||
|
/// Session lock requested; waiting for the compositor's `locked` event
|
||||||
|
/// before the lock surface exists.
|
||||||
|
PendingLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceKind
|
impl SurfaceKind
|
||||||
@@ -81,9 +87,10 @@ impl SurfaceKind
|
|||||||
SurfaceKind::Layer( l ) => l.wl_surface(),
|
SurfaceKind::Layer( l ) => l.wl_surface(),
|
||||||
SurfaceKind::Window( w ) => w.wl_surface(),
|
SurfaceKind::Window( w ) => w.wl_surface(),
|
||||||
SurfaceKind::Popup( p ) => p.wl_surface(),
|
SurfaceKind::Popup( p ) => p.wl_surface(),
|
||||||
|
SurfaceKind::Lock( s ) => s.wl_surface(),
|
||||||
// Unreachable: draw_frame is only called when configured == true,
|
// Unreachable: draw_frame is only called when configured == true,
|
||||||
// which only becomes true after on_configure, which requires a real surface.
|
// which only becomes true after on_configure, which requires a real surface.
|
||||||
SurfaceKind::Pending( .. ) => unreachable!( "surface not yet created" ),
|
SurfaceKind::Pending( .. ) | SurfaceKind::PendingLock => unreachable!( "surface not yet created" ),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +105,8 @@ impl SurfaceKind
|
|||||||
SurfaceKind::Layer( l ) => Some( l.wl_surface() ),
|
SurfaceKind::Layer( l ) => Some( l.wl_surface() ),
|
||||||
SurfaceKind::Window( w ) => Some( w.wl_surface() ),
|
SurfaceKind::Window( w ) => Some( w.wl_surface() ),
|
||||||
SurfaceKind::Popup( p ) => Some( p.wl_surface() ),
|
SurfaceKind::Popup( p ) => Some( p.wl_surface() ),
|
||||||
SurfaceKind::Pending( .. ) => None,
|
SurfaceKind::Lock( s ) => Some( s.wl_surface() ),
|
||||||
|
SurfaceKind::Pending( .. ) | SurfaceKind::PendingLock => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ impl<Msg: Clone> Element<Msg>
|
|||||||
match self
|
match self
|
||||||
{
|
{
|
||||||
Element::Button( b ) => b.focusable,
|
Element::Button( b ) => b.focusable,
|
||||||
Element::TextEdit( _ ) | Element::Slider( _ ) | Element::VSlider( _ ) => true,
|
Element::TextEdit( t ) => !t.read_only,
|
||||||
|
Element::Slider( _ ) | Element::VSlider( _ ) => true,
|
||||||
Element::Toggle( _ ) | Element::Checkbox( _ ) | Element::Radio( _ ) | Element::ListItem( _ ) => true,
|
Element::Toggle( _ ) | Element::Checkbox( _ ) | Element::Radio( _ ) | Element::ListItem( _ ) => true,
|
||||||
Element::WindowButton( b ) => b.focusable,
|
Element::WindowButton( b ) => b.focusable,
|
||||||
_ => false,
|
_ => false,
|
||||||
@@ -198,7 +199,7 @@ impl<Msg: Clone> Element<Msg>
|
|||||||
|
|
||||||
pub fn is_text_input( &self ) -> bool
|
pub fn is_text_input( &self ) -> bool
|
||||||
{
|
{
|
||||||
matches!( self, Element::TextEdit( _ ) )
|
matches!( self, Element::TextEdit( t ) if !t.read_only )
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cursor shape to display while the pointer is over this widget.
|
/// Cursor shape to display while the pointer is over this widget.
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl<Msg: Clone> ListItem<Msg>
|
|||||||
|
|
||||||
/// Attach a leading icon. Pass the decoded RGBA buffer alongside
|
/// Attach a leading icon. Pass the decoded RGBA buffer alongside
|
||||||
/// the image's native width and height; the draw path scales it
|
/// the image's native width and height; the draw path scales it
|
||||||
/// down to [`theme::ICON_SIZE`] on the same row baseline as the
|
/// down to `theme::ICON_SIZE` on the same row baseline as the
|
||||||
/// label. Symbolic icons should be pre-tinted by the caller (see
|
/// label. Symbolic icons should be pre-tinted by the caller (see
|
||||||
/// [`crate::tint_symbolic`]).
|
/// [`crate::tint_symbolic`]).
|
||||||
pub fn icon( mut self, rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> Self
|
pub fn icon( mut self, rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> Self
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ pub struct TextEdit<Msg: Clone>
|
|||||||
/// has `secure( true )` and the explicit flag becomes redundant
|
/// has `secure( true )` and the explicit flag becomes redundant
|
||||||
/// — the toggle controls the visibility from then on.
|
/// — the toggle controls the visibility from then on.
|
||||||
pub password_toggle: Option<( bool, Msg )>,
|
pub password_toggle: Option<( bool, Msg )>,
|
||||||
|
/// When `true`, the field renders its box and value but takes no keyboard
|
||||||
|
/// focus and accepts no input — a read-only display styled as a text field.
|
||||||
|
pub read_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Msg: Clone> TextEdit<Msg>
|
impl<Msg: Clone> TextEdit<Msg>
|
||||||
@@ -181,6 +184,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
font_size: theme::FONT_SIZE,
|
font_size: theme::FONT_SIZE,
|
||||||
select_on_focus: false,
|
select_on_focus: false,
|
||||||
password_toggle: None,
|
password_toggle: None,
|
||||||
|
read_only: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +360,14 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the field read-only: shows the value styled as a field but
|
||||||
|
/// takes no focus and accepts no input.
|
||||||
|
pub fn read_only( mut self, on: bool ) -> Self
|
||||||
|
{
|
||||||
|
self.read_only = on;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Assign a stable identifier for focus management.
|
/// Assign a stable identifier for focus management.
|
||||||
pub fn id( mut self, id: WidgetId ) -> Self
|
pub fn id( mut self, id: WidgetId ) -> Self
|
||||||
{
|
{
|
||||||
@@ -487,6 +499,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
font_size: self.font_size,
|
font_size: self.font_size,
|
||||||
select_on_focus: self.select_on_focus,
|
select_on_focus: self.select_on_focus,
|
||||||
password_toggle: self.password_toggle.clone().map( |( v, m )| ( v, ( *f )( m ) ) ),
|
password_toggle: self.password_toggle.clone().map( |( v, m )| ( v, ( *f )( m ) ) ),
|
||||||
|
read_only: self.read_only,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user