Wayland Layer Shell
Guido uses the Wayland layer shell protocol for positioning widgets on the desktop. This enables status bars, panels, overlays, and multi-surface applications.
Surface Configuration
Each surface is configured using SurfaceConfig:
extern crate guido;
use guido::prelude::*;
fn view() -> Container { container() }
fn main() {
App::new().run(|app| {
let _surface_id = app.add_surface(
SurfaceConfig::new()
.width(1920)
.height(32)
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.layer(Layer::Top)
.keyboard_interactivity(KeyboardInteractivity::OnDemand)
.namespace("my-status-bar")
.background_color(Color::rgb(0.1, 0.1, 0.15)),
|| view(),
);
});
;
}
Note: run() takes a setup closure where you add surfaces. add_surface() returns a SurfaceId that can be used to get a SurfaceHandle for dynamic property modification; the surface’s own tree reads its handle with expect_context::<SurfaceHandle>() instead (see A Surface’s Own Handle).
Layers
Control where your surface appears in the stacking order:
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new().layer(Layer::Top)
;
}
| Layer | Description |
|---|---|
Background | Below all windows |
Bottom | Above background, below windows |
Top | Above windows (default) |
Overlay | Above everything |
Use Cases
- Background: Desktop widgets, wallpaper effects
- Bottom: Dock bars (below windows but above background)
- Top: Status bars, panels (above windows)
- Overlay: Notifications, lock screens
Keyboard Interactivity
Control how the surface receives keyboard focus:
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new().keyboard_interactivity(KeyboardInteractivity::OnDemand)
;
}
| Mode | Description |
|---|---|
None | Surface never receives keyboard focus |
OnDemand | Surface receives focus when clicked (default) |
Exclusive | Surface grabs keyboard focus exclusively |
Use Cases
- None: Status bars that only respond to mouse
- OnDemand: Panels with text input fields
- Exclusive: Lock screens, app launchers, modal dialogs
Anchoring
Control which screen edges the surface attaches to:
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new().anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
;
}
| Anchor | Effect |
|---|---|
TOP | Attach to top edge |
BOTTOM | Attach to bottom edge |
LEFT | Attach to left edge |
RIGHT | Attach to right edge |
Common Patterns
Top status bar (full width):
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new()
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.height(32)
;
}
Bottom dock (full width):
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new()
.anchor(Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT)
.height(48)
;
}
Left sidebar (full height):
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new()
.anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT)
.width(64)
;
}
Corner widget (top-right):
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new()
.anchor(Anchor::TOP | Anchor::RIGHT)
.width(200)
.height(100)
;
}
Centered floating (no anchors):
extern crate guido;
use guido::prelude::*;
fn main() {
// No anchor = centered on screen
SurfaceConfig::new()
.width(400)
.height(300)
;
}
Size Behavior
Size depends on anchoring:
- Anchored dimension: Expands to fill (e.g., width when LEFT+RIGHT anchored)
- Unanchored dimension: Uses specified size
- No anchors: Uses exact size, centered on screen
extern crate guido;
use guido::prelude::*;
fn main() {
// Width fills screen, height is 32px
SurfaceConfig::new()
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.height(32);
// Both dimensions specified, widget is 200x100
SurfaceConfig::new()
.anchor(Anchor::TOP | Anchor::RIGHT)
.width(200)
.height(100)
;
}
Namespace
Identify your surface to the compositor:
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new().namespace("my-app-name")
;
}
Some compositors use this for:
- Workspace rules
- Blur effects
- Per-app settings
Exclusive Zones
Reserve screen space (windows won’t overlap):
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new()
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.height(32)
.exclusive_zone(32) // Reserve 32px at top
;
}
Without exclusive zone, windows can cover the surface.
The compositor adds a margin on the anchored edge on top of the zone (wlroots
compositors such as sway, and smithay ones such as niri, both do), so
.margin([8, 0, 0, 0]) with .exclusive_zone(32) keeps windows 40px from the
top. ExclusiveZone::Auto reserves the surface’s own size plus the margin facing
away from the edge: a gap below a top bar is .margin([0, 0, 8, 0]).
Multi-Surface Applications
Guido supports creating multiple surfaces within a single application. All surfaces share the same reactive state, allowing for coordinated updates.
Multiple Static Surfaces
Define multiple surfaces at startup:
extern crate guido;
use guido::prelude::*;
fn main() {
App::new().run(|app| {
// Shared reactive state
let count = create_signal(0);
// Top status bar
app.add_surface(
SurfaceConfig::new()
.height(32)
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.layer(Layer::Top)
.namespace("status-bar")
.background_color(Color::rgb(0.1, 0.1, 0.15)),
move || {
container()
.height(fill())
.layout(
Flex::row()
.main_alignment(MainAlignment::SpaceBetween)
.cross_alignment(CrossAlignment::Center)
)
.padding([0.0, 16.0])
.child(text("Status Bar"))
.child(text(move || format!("Count: {}", count.get())))
},
);
// Bottom dock
app.add_surface(
SurfaceConfig::new()
.height(48)
.anchor(Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT)
.layer(Layer::Top)
.namespace("dock")
.background_color(Color::rgb(0.15, 0.15, 0.2)),
move || {
container()
.height(fill())
.layout(Flex::row().spacing(16.0).center())
.child(
container()
.padding([8.0, 16.0])
.background(Color::rgb(0.3, 0.3, 0.4))
.corners(8.0)
.when_hovered(|s| s.lighter(0.1))
.on_click(move || count.update(|c| *c += 1))
.child(container().child(text("+").color(Color::WHITE)))
)
},
);
});
}
Key Points
- Shared State: All surfaces share the same reactive signals
- Independent Widget Trees: Each surface has its own widget tree
- Fill Layout: Use
height(fill())to make containers expand to fill the surface
Dynamic Surfaces
Create and destroy surfaces at runtime using spawn_surface():
extern crate guido;
use guido::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
App::new().run(|app| {
let popup_handle: Rc<RefCell<Option<SurfaceHandle>>> = Rc::new(RefCell::new(None));
let popup_clone = popup_handle.clone();
app.add_surface(
SurfaceConfig::new()
.height(32)
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT),
move || {
container()
.child(
container()
.padding(8.0)
.when_hovered(|s| s.lighter(0.1))
.on_click({
let popup_handle = popup_clone.clone();
move || {
let mut handle = popup_handle.borrow_mut();
if let Some(h) = handle.take() {
// Close existing popup
h.close();
} else {
// Create new popup
let new_handle = spawn_surface(
SurfaceConfig::new()
.width(200)
.height(300)
.anchor(Anchor::TOP | Anchor::RIGHT)
.layer(Layer::Overlay)
.keyboard_interactivity(KeyboardInteractivity::Exclusive),
|| {
container()
.padding(16.0)
.child(text("Popup Content"))
}
);
*handle = Some(new_handle);
}
}
})
.child(text("Toggle Popup"))
)
},
);
});
}
SurfaceHandle API
The SurfaceHandle allows controlling a surface after creation:
// not compiled: a signature listing — these declarations have no bodies.
impl SurfaceHandle {
/// Close and destroy the surface. Input already queued for it is
/// not delivered once this is called. On a lock cover it does nothing
/// but log a warning: `unlock_session()` is what takes covers away
pub fn close(&self);
/// Get the surface ID
pub fn id(&self) -> SurfaceId;
/// Change the layer (Background, Bottom, Top, Overlay)
pub fn set_layer(&self, layer: Layer);
/// Change keyboard interactivity mode
pub fn set_keyboard_interactivity(&self, mode: KeyboardInteractivity);
/// Change anchor edges
pub fn set_anchor(&self, anchor: Anchor);
/// Change surface size — same vocabulary as `SurfaceConfig::width`,
/// so an axis can be handed back to `content()`
pub fn set_size(&self, width: impl Into<SurfaceExtent>, height: impl Into<SurfaceExtent>);
/// Change the screen-space reservation policy
pub fn set_exclusive_zone(&self, zone: impl Into<ExclusiveZone>);
/// Change margins — CSS shorthands, as `padding` takes
pub fn set_margin(&self, margin: impl Into<Margin>);
}
A Surface’s Own Handle
Every surface’s scope declares its own SurfaceHandle, so a tree can reach
the surface it is in without being handed its id. Read it with
expect_context in the factory body, where the scope is current, and capture
it into the handlers that use it — a handler opens no scope of its own (see
Context):
extern crate guido;
use guido::prelude::*;
fn main() {
App::new().run(|app| {
app.add_surface(
SurfaceConfig::new()
.width(240)
.height(32)
.anchor(Anchor::TOP | Anchor::RIGHT),
|| {
let surface = expect_context::<SurfaceHandle>();
let open = create_signal(false);
container()
.on_click(move || {
open.update(|o| *o = !*o);
surface.set_size(240, if open.get() { 400 } else { 32 });
})
.child(text("Toggle"))
},
);
});
;
}
The same holds for a surface from spawn_surface(), a popup from
spawn_popup() and a lock cover from lock_session(). A popup’s handle is its
own: a tree inside a popup reads the popup’s id, not its parent’s. A factory that needs the parent’s id — to
hang a popup from it — reads the parent’s handle where the parent’s tree is
built and captures it.
Getting a Handle for Existing Surfaces
Use surface_handle() to get a handle for any surface by its ID, from outside
it:
extern crate guido;
use guido::prelude::*;
fn main() {
let config = SurfaceConfig::new();
App::new().run(|app| {
// Store the ID when adding the surface
let status_bar_id = app.add_surface(config, move || {
container().child(text("Status bar"))
});
// Get a handle and modify properties dynamically, from anywhere
let handle = surface_handle(status_bar_id);
handle.set_layer(Layer::Overlay);
handle.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
});
;
}
Multiple Outputs (Monitors)
Connected outputs are exposed as a reactive list. Reading it inside a tracked closure (an effect, a dynamic child, a reactive property) re-runs the closure when monitors are plugged in, unplugged, or reconfigured:
extern crate guido;
fn main() {
use guido::prelude::*;
create_effect(move || {
for info in outputs().get() {
println!(
"{:?}: {:?} {} ({}x{:?})",
info.id, info.name, info.model, info.scale_factor, info.logical_size
);
}
});
;
}
Each OutputInfo carries a stable OutputId plus the connector name
("DP-1", "eDP-1", …), description, make/model, integer scale factor,
and logical size/position when the compositor reports them. Ids are never
reused: a monitor that is unplugged and reconnected gets a fresh id.
Display Scale
Scaling is automatic and there is nothing to configure. Widgets are laid
out in logical pixels and the surface is drawn at whatever scale its
output is set to — 1.5 and 1.25 included, where the compositor offers
wp_fractional_scale_v1 and wp_viewporter. Where it does not, the
surface falls back to the whole-number scale the compositor asks for.
OutputInfo::scale_factor is not that number. It is the integer
wl_output scale, which is what a compositor reports for a monitor and
is rounded up on a fractionally-scaled one — a 1.5 output reports 2. It
is there to describe the monitor, not to size anything.
Pinning a Surface to an Output
By default the compositor picks the output a surface appears on. Pass an
OutputId to pin it:
extern crate guido;
use guido::prelude::*;
fn bar_widget() -> Container { container() }
fn main() {
for info in outputs().get() {
spawn_surface(
SurfaceConfig::new()
.height(32)
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.output(info.id),
move || bar_widget(),
);
}
}
The output cannot be changed after creation — a layer surface is bound to its output for its lifetime. If the output disconnects before the surface is created, the compositor chooses one instead (a warning is logged).
One Bar per Monitor
An app can start with zero surfaces and spawn one per output from an
effect — the classic multi-monitor status bar. See
examples/multi_output.rs for the full version:
extern crate guido;
use guido::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
fn bar_widget() -> Container { container() }
fn main() {
App::new().run(|_app| {
let bars: Rc<RefCell<HashMap<OutputId, SurfaceHandle>>> =
Rc::new(RefCell::new(HashMap::new()));
create_effect(move || {
let current = outputs().get();
let mut bars = bars.borrow_mut();
// Drop bars for disconnected outputs
bars.retain(|id, handle| {
let alive = current.iter().any(|o| o.id == *id);
if !alive {
handle.close();
}
alive
});
// Spawn a bar on every new output
for info in current {
if !bars.contains_key(&info.id) {
let handle = spawn_surface(
SurfaceConfig::new().height(32).output(info.id),
move || bar_widget(),
);
bars.insert(info.id, handle);
}
}
});
});
;
}
When the compositor closes the surfaces of an unplugged monitor, the
bars keep working on the remaining outputs. When the last monitor goes,
its bar is the last surface, and closing the last surface ends the app —
unless it was built with App::quit_on_last_surface(false), in which case
it idles until a monitor comes back and the effect spawns a bar for it.
Tracking Which Output a Surface Is On
surface_output(surface_id) reports the output a surface is currently
shown on (None until the compositor maps it). It is a tracked read —
reactive inside any tracked closure:
extern crate guido;
use guido::prelude::*;
fn main() {
let my_surface_id = SurfaceId::next();
text(move || match surface_output(my_surface_id) {
Some(out) => format!("shown on output {}", out.raw()),
None => "not mapped yet".to_string(),
})
;
}
For a surface spanning multiple outputs, this reports the one entered most recently.
Input Regions
By default the whole surface accepts pointer and touch input. A container can say otherwise, and what it says goes to the compositor: everything outside the region lets clicks reach the windows below.
The surface declares the baseline and a container declares the exception. An overlay that lets everything past, with one pill that does not:
extern crate guido;
use guido::prelude::*;
fn main() {
SurfaceConfig::new()
.background_color(Color::TRANSPARENT)
.click_through();
container()
.takes_input(true)
.corners(20.0)
.on_click(|| {})
;
}
And the other way round, a bar that takes input with a notch the desktop gets:
extern crate guido;
use guido::prelude::*;
fn main() {
container().takes_input(false).width(200.0)
;
}
The region is read off the frame that was drawn, so it follows the shape on
screen: its corners, its transform, its clip. A container that stops painting —
hidden, culled, scrolled out of view — takes its region with it, and a frame
that changes nothing publishes nothing. examples/input_region.rs is the first
of the two, in full.
The transform half of that is newer than the sentence. A turned container’s region was the axis-aligned box around it, which claims more than it drew: at 45° an 80×80 container takes clicks over 56% more area than it covers, and every one of those clicks lands on a panel that is not there rather than reaching the window below. The region is cut from the placed shape now, and so is whatever clips it — so a card filling a rounded scroller takes clicks in the scroller’s rounded shape rather than in the square around it.
What a container declares is its own shape. Children are covered because
they are drawn inside it, not because the declaration reaches them. A child
drawn outside its parent — overflow is Visible by default, so a transform
or a size larger than the parent does it — is outside the region as well, and on
a click-through surface a handler on that child never runs:
extern crate guido;
use guido::prelude::*;
fn main() {
let do_thing = || {};
container()
.takes_input(true)
.width(100.0)
.height(30.0)
// 40px below the parent, so outside the region: those clicks go to the
// desktop, not to this handler.
.child(container().translate((0.0, 40.0)).on_click(do_thing))
;
}
Declare takes_input on the container whose shape you mean, and a nested
declaration of the opposite kind is simply a smaller area declared later — an
island inside a hole works with no rule of its own.
takes_input(false) is not the same as
enabled(false): a disabled container
refuses input and still blocks what is behind the surface, which is what a
disabled button should do.
For a region that belongs to no widget, the surface still takes rectangles directly:
extern crate guido;
use guido::prelude::*;
fn main() {
let id = SurfaceId::next();
let rect = Rect::new(16.0, 20.0, 200.0, 40.0);
SurfaceConfig::new().input_region([Rect::new(16.0, 20.0, 200.0, 40.0)]);
surface_handle(id).set_input_region(Some(vec![rect]));
surface_handle(id).set_input_region(None);
;
}
Note: keyboard focus is unaffected — use keyboard_interactivity for
that. Rectangles are rounded outward to whole pixels.
Backdrop Blur
backdrop_blur blurs whatever is behind a container. There are two
things behind it, on opposite sides of the Wayland surface, and both
are filtered:
- what this surface already drew — a wallpaper, a photo, the panel under a card;
- what the compositor composites below the surface, through
ext-background-effect-v1, wherever the surface is translucent.
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.background(Color::rgba(0.12, 0.12, 0.18, 0.55))
.corners(16.0)
.backdrop_blur(32.0)
.child(text("Frosted glass"))
;
}
Filtering both is not a compromise between mechanisms. Blur is a linear
operator, so over a region of uniform alpha a:
blur(a·ours + (1−a)·theirs) = a·blur(ours) + (1−a)·blur(theirs)
Blurring each layer separately and compositing gives exactly the same result — which is why neither is chosen over the other. A translucent panel is neither “ours” nor “theirs” at any pixel, so a per-box choice could only be right for some of them.
Restrict it when only one side should soften:
extern crate guido;
use guido::prelude::*;
fn main() {
// Desktop behind the panel softens; the panel's own islands stay crisp.
container().backdrop_blur(
BackdropBlur::new(24.0).sources(BackdropSources::COMPOSITOR),
)
;
}
What each side costs
The compositor side is a region handed over once per change: the
region follows the container automatically as layout moves, resizes and
(animated) corner radii change. Rounded corners are tessellated into
small rectangles because wl_region has no notion of curves, and the
protocol carries no radius — the compositor picks its own, so radius
does not apply there. Check availability with compositor_effects().
Which is why a blur restricted to the compositor asks for its region on its sources alone, and a radius of zero there is a request rather than a refusal:
extern crate guido;
use guido::prelude::*;
fn main() {
// Blur nothing of our own; let the compositor blur the desktop behind us.
container().backdrop_blur(
BackdropBlur::new(0.0).sources(BackdropSources::COMPOSITOR),
)
;
}
Everywhere the surface is a source, a radius of zero still means no blur — that is how a blur is switched off by the same signal that switches it on. To switch off a compositor-only one, empty its sources instead.
The surface side is guido’s own: the frame is drawn into an
offscreen target, each region is downsampled, blurred with a separable
gaussian and composited back masked to the container’s rounded shape.
Frames with no backdrop blur skip all of it and draw straight to the
swapchain. A text can ask for the same thing with the shape of its
glyphs as the mask — see Frosted glass
— and that one is surface-side only, since a wl_region cannot hold a
letter.
Two skips keep that honest, and both are unobservable — they drop work that could not have changed a pixel, so they may switch on and off between frames without anything showing:
- a region with nothing drawn beneath it is not filtered at all;
- surfaces that never asked for a compositor region are left untouched, so compositor-side rules (e.g. blur by namespace) keep working.
The seam
Where alpha varies within the blur radius — at the edges of opaque
content inside the box — the two blurs do not bleed into each other:
the desktop does not smear into the surface’s own content or the other
way round. That cannot be fixed from guido. set_blur_region is
fire-and-forget: it takes a region and returns nothing, so the
compositor’s pixels are never ours to filter across.
See examples/blur_example.rs and examples/draw_order_example.rs.
Popups (Menus, Dropdowns)
spawn_popup creates an xdg popup anchored to a surface: the
compositor positions it relative to an anchor rectangle, keeps it on
screen (flipping/sliding at screen edges), and — with .grab() —
dismisses it when the user clicks outside. Real menu semantics, no
fullscreen overlay:
extern crate guido;
use guido::prelude::*;
fn menu_widget() -> Container { container() }
fn main() {
let bar_id = SurfaceId::next();
let button_ref = create_widget_ref();
let popup = spawn_popup(
bar_id,
PopupConfig::new(250) // width; height sizes to content
.anchor_rect(button_ref.rect().get()) // parent surface coords
.anchor(PopupAnchor::Bottom) // attach point on the rect
.gravity(PopupGravity::Bottom) // growth direction
.grab(), // dismiss on outside click
move || menu_widget(),
);
;
}
Dismissal is reactive — reset your open/closed state when the compositor closes the popup:
extern crate guido;
use guido::prelude::*;
fn main() {
let menu_open = create_signal(false);
let popup = spawn_popup(SurfaceId::next(), PopupConfig::new(250), container);
create_effect(move || {
if popup.dismissed() {
menu_open.set(false);
}
});
;
}
popup.close() closes it programmatically, and from that call on
neither it nor a popup hanging from it is handed any more input — a
second click already queued on the item that closed the menu picks
nothing. Popups render their own
widget tree and share the app’s reactive state like any surface;
anchoring a popup to another popup creates a nested popup (submenus).
A handle outlives its popup: reading dismissed() is safe after the
popup is gone, including from a submenu whose parent closed with it.
Note: for a bottom bar use anchor(Top) + gravity(Top) so the menu
opens upward — or just rely on the compositor’s flip adjustment.
See examples/popup_example.rs.
Session Lock (Lock Screens)
lock_session asks the compositor to lock the session
(ext-session-lock-v1) and, without waiting for the answer, creates
one lock surface per output using your widget factory, as the protocol
asks. Once the compositor grants the lock it blanks every output, shows
the lock surfaces, and routes all input to them, so a password_input
works out of the box:
extern crate guido;
use guido::prelude::*;
fn verify_password(_attempt: &str) -> bool { true }
fn main() {
lock_session(|output: OutputInfo| {
let attempt = create_password();
container()
.width(fill())
.height(fill())
.background(Color::rgb(0.07, 0.07, 0.1))
.layout(Flex::column().main_alignment(MainAlignment::Center))
.child(text(format!("Locked — {:?}", output.name)))
.child(password_input(attempt).on_submit(move || {
if verify_password(attempt.take().expose()) {
unlock_session();
}
}))
});
;
}
The lifecycle is reactive:
text(move || format!("{:?}", lock_state().get()))
// Unlocked → Locking → Locked, back to Unlocked on unlock/denial
Details worth knowing:
- Outputs plugged in while locking or locked get a lock surface automatically (the factory is called again); the compositor blanks any output without one.
- If the compositor refuses the lock (no protocol support, or another
lock client is active),
lock_statereturns toUnlockedand the lock surfaces already made are dropped. - A lock surface lasts as long as the lock.
close()on itsSurfaceHandledoes nothing but log a warning — a dismiss button, or a component written for ordinary surfaces, cannot leave an output uncovered — andunlock_sessionis the way out. - Unlike ordinary surfaces, taking lock surfaces away never exits the
app: a lock daemon whose only surfaces are lock surfaces keeps running
after unlocking, idle until the next
lock_sessioncall. - Layer-shell properties (anchor, margins, exclusive zone…) don’t apply to lock surfaces; their size always comes from the compositor.
See examples/simple_lock.rs — note that running it really locks your
session (it has a 30-second auto-unlock safety net; the password is
guido).
Complete Examples
Status Bar
extern crate guido;
use guido::prelude::*;
fn center_section() -> Container { container() }
fn left_section() -> Container { container() }
fn right_section() -> Container { container() }
fn main() {
App::new().run(|app| {
app.add_surface(
SurfaceConfig::new()
.height(32)
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT)
.layer(Layer::Top)
.exclusive_zone(32u32)
.namespace("status-bar")
.background_color(Color::rgb(0.1, 0.1, 0.15)),
|| {
container()
.height(fill())
.layout(
Flex::row()
.main_alignment(MainAlignment::SpaceBetween)
.cross_alignment(CrossAlignment::Center)
)
.children([
left_section(),
center_section(),
right_section(),
])
},
);
});
}
Dock
extern crate guido;
use guido::prelude::*;
fn dock_icon(_name: &str) -> Container { container() }
fn main() {
App::new().run(|app| {
app.add_surface(
SurfaceConfig::new()
.height(64)
.anchor(Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT)
.layer(Layer::Top)
.exclusive_zone(64u32)
.namespace("dock")
.background_color(Color::rgba(0.1, 0.1, 0.15, 0.9)),
|| {
container()
.height(fill())
.layout(Flex::row().spacing(8.0).center())
.children([
dock_icon("terminal"),
dock_icon("browser"),
dock_icon("files"),
])
},
);
});
}
Floating Overlay with Keyboard Focus
extern crate guido;
use guido::prelude::*;
fn main() {
App::new().run(|app| {
app.add_surface(
SurfaceConfig::new()
.width(300)
.height(100)
.anchor(Anchor::TOP | Anchor::RIGHT)
.layer(Layer::Overlay)
.keyboard_interactivity(KeyboardInteractivity::Exclusive)
.namespace("notification")
.background_color(Color::TRANSPARENT),
|| {
container()
.padding(20.0)
.background(Color::rgb(0.15, 0.15, 0.2))
.corners(12.0)
.child(container().child(text("Notification").color(Color::WHITE)))
},
);
});
}
API Reference
SurfaceConfig
// not compiled: a signature listing — these declarations have no bodies.
impl SurfaceConfig {
pub fn new() -> Self;
pub fn width(self, width: impl Into<SurfaceExtent>) -> Self;
pub fn height(self, height: impl Into<SurfaceExtent>) -> Self;
pub fn anchor(self, anchor: Anchor) -> Self;
pub fn layer(self, layer: Layer) -> Self;
pub fn keyboard_interactivity(self, mode: KeyboardInteractivity) -> Self;
pub fn exclusive_zone(self, zone: impl Into<ExclusiveZone>) -> Self;
pub fn namespace(self, namespace: impl Into<String>) -> Self;
pub fn background_color(self, color: Color) -> Self;
pub fn margin(self, margin: impl Into<Margin>) -> Self;
pub fn output(self, output: OutputId) -> Self;
pub fn input_region(self, rects: impl Into<Vec<Rect>>) -> Self;
pub fn click_through(self) -> Self;
}
Outputs
// not compiled: a signature listing — these declarations have no bodies.
/// Reactive list of connected outputs, sorted by id
pub fn outputs() -> Signal<Vec<OutputInfo>>;
/// The output a surface is currently shown on (tracked read)
pub fn surface_output(id: SurfaceId) -> Option<OutputId>;
pub struct OutputInfo {
pub id: OutputId,
pub name: Option<String>, // connector, e.g. "DP-1"
pub description: Option<String>,
pub make: String,
pub model: String,
pub scale_factor: i32,
pub logical_size: Option<(i32, i32)>,
pub logical_position: Option<(i32, i32)>,
}
App
// not compiled: a signature listing — these declarations have no bodies.
impl App {
pub fn new() -> Self;
pub fn run(self, setup: impl FnOnce(&mut Self)) -> ExitReason;
pub fn add_surface<W, F>(&mut self, config: SurfaceConfig, widget_fn: F) -> SurfaceId
where
W: Widget + 'static,
F: FnOnce() -> W + 'static;
}
Dynamic Surface Creation
// not compiled: a signature listing — these declarations have no bodies.
/// Spawn a new surface at runtime
pub fn spawn_surface<W, F>(config: SurfaceConfig, widget_fn: F) -> SurfaceHandle
where
W: Widget + 'static,
F: FnOnce() -> W + Send + 'static;
/// Get a handle for an existing surface by ID
pub fn surface_handle(id: SurfaceId) -> SurfaceHandle;
SurfaceHandle
// not compiled: a signature listing — these declarations have no bodies.
impl SurfaceHandle {
pub fn id(&self) -> SurfaceId;
pub fn close(&self);
pub fn set_layer(&self, layer: Layer);
pub fn set_keyboard_interactivity(&self, mode: KeyboardInteractivity);
pub fn set_anchor(&self, anchor: Anchor);
pub fn set_size(&self, width: impl Into<SurfaceExtent>, height: impl Into<SurfaceExtent>);
pub fn set_exclusive_zone(&self, zone: impl Into<ExclusiveZone>);
pub fn set_margin(&self, margin: impl Into<Margin>);
pub fn set_input_region(&self, rects: Option<Vec<Rect>>);
}