mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-12-12 13:25:54 +00:00
feat: add surface config runtime maniputation example
This commit is contained in:
parent
c2064c6012
commit
da4871e4b9
7 changed files with 441 additions and 4 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -3092,6 +3092,15 @@ version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "runtime-surface-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"layer-shika",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ members = [
|
||||||
"examples/multi-surface",
|
"examples/multi-surface",
|
||||||
"examples/declarative-config",
|
"examples/declarative-config",
|
||||||
"examples/event-loop",
|
"examples/event-loop",
|
||||||
|
"examples/runtime-surface-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ Each example is a standalone crate that can be run from anywhere in the workspac
|
||||||
cargo run -p simple-bar
|
cargo run -p simple-bar
|
||||||
cargo run -p multi-surface
|
cargo run -p multi-surface
|
||||||
cargo run -p declarative-config
|
cargo run -p declarative-config
|
||||||
|
cargo run -p runtime-surface-config
|
||||||
# Or with logging
|
|
||||||
RUST_LOG=info cargo run -p simple-bar
|
|
||||||
|
|
||||||
# Or from the example directory
|
# Or from the example directory
|
||||||
cd examples/simple-bar
|
cd examples/simple-bar
|
||||||
|
|
@ -28,6 +26,7 @@ cargo run
|
||||||
2. **multi-surface** - Learn about multiple surfaces and callbacks
|
2. **multi-surface** - Learn about multiple surfaces and callbacks
|
||||||
3. **declarative-config** - See the alternative configuration approach
|
3. **declarative-config** - See the alternative configuration approach
|
||||||
4. **event-loop** - Explore event loop integration with timers and channels
|
4. **event-loop** - Explore event loop integration with timers and channels
|
||||||
|
5. **runtime-surface-config** - Surface configuration manipulation at runtime
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
|
|
@ -46,7 +45,7 @@ All examples use layer-shika's `Result<()>` type for error handling with the `?`
|
||||||
## Coming Soon
|
## Coming Soon
|
||||||
|
|
||||||
Additional examples demonstrating:
|
Additional examples demonstrating:
|
||||||
- Event loop integration (timers, channels, custom event sources)
|
|
||||||
- Multi-output support (multiple monitors) with different surfaces per output
|
- Multi-output support (multiple monitors) with different surfaces per output
|
||||||
- Advanced popup patterns
|
- Advanced popup patterns
|
||||||
- Dynamic UI loading
|
- Dynamic UI loading
|
||||||
|
|
|
||||||
14
examples/runtime-surface-config/Cargo.toml
Normal file
14
examples/runtime-surface-config/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "runtime-surface-config"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
layer-shika = { path = "../.." }
|
||||||
|
env_logger = "0.11.7"
|
||||||
|
log.workspace = true
|
||||||
69
examples/runtime-surface-config/README.md
Normal file
69
examples/runtime-surface-config/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Runtime Surface Config Example
|
||||||
|
|
||||||
|
This example demonstrates layer-shika's runtime surface configuration capabilities using the Surface Control API.
|
||||||
|
|
||||||
|
## Features Demonstrated
|
||||||
|
|
||||||
|
1. **Dynamic Sizing**: Toggle between compact (32px) and expanded (64px) bar heights
|
||||||
|
2. **Anchor Position Control**: Switch between top and bottom screen edges at runtime
|
||||||
|
3. **Layer Management**: Cycle through Background, Bottom, Top, and Overlay layers
|
||||||
|
4. **Channel-based UI Updates**: Use event loop channels to update UI state from callbacks
|
||||||
|
5. **Surface Control API**: Manipulate surfaces via callback handlers
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
- **Expand/Collapse Button**: Toggle between 32px and 64px bar heights
|
||||||
|
- **Switch Anchor**: Toggle between top and bottom screen positions
|
||||||
|
- **Switch Layer**: Cycle through Background → Bottom → Top → Overlay layers
|
||||||
|
|
||||||
|
## Running the Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run -p runtime-surface-config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Highlights
|
||||||
|
|
||||||
|
### Control from Slint Callbacks
|
||||||
|
|
||||||
|
```rust
|
||||||
|
shell.on("Bar", "toggle-size", move |control| {
|
||||||
|
let bar = control.surface("Bar");
|
||||||
|
bar.resize(width, height)?;
|
||||||
|
bar.set_exclusive_zone(new_size)?;
|
||||||
|
Value::Struct(Struct::from_iter([("expanded".into(), is_expanded.into())]))
|
||||||
|
})?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channel-based UI Updates
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let (_token, sender) = handle.add_channel(|message: UiUpdate, app_state| {
|
||||||
|
for surface in app_state.all_outputs() {
|
||||||
|
let component = surface.component_instance();
|
||||||
|
match &message {
|
||||||
|
UiUpdate::IsExpanded(is_expanded) => {
|
||||||
|
component.set_property("is-expanded", (*is_expanded).into())?;
|
||||||
|
}
|
||||||
|
// ... other updates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Patterns
|
||||||
|
|
||||||
|
This example showcases the Surface Control API pattern:
|
||||||
|
|
||||||
|
**SurfaceControlHandle** (channel-based):
|
||||||
|
|
||||||
|
- Accessible via `control.surface(name)` in callback handlers
|
||||||
|
- Safe to call from Slint callbacks
|
||||||
|
- Commands execute asynchronously in event loop
|
||||||
|
|
||||||
|
**Event Loop Channels**:
|
||||||
|
|
||||||
|
- Use `add_channel` to create message handlers
|
||||||
|
- Send messages from callbacks to update UI state
|
||||||
|
- Process messages in event loop context
|
||||||
|
- Access all surfaces via `app_state.all_outputs()`
|
||||||
287
examples/runtime-surface-config/src/main.rs
Normal file
287
examples/runtime-surface-config/src/main.rs
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
use layer_shika::calloop::channel::Sender;
|
||||||
|
use layer_shika::prelude::*;
|
||||||
|
use layer_shika::slint::SharedString;
|
||||||
|
use layer_shika::slint_interpreter::{Struct, Value};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum UiUpdate {
|
||||||
|
IsExpanded(bool),
|
||||||
|
CurrentAnchor(String),
|
||||||
|
CurrentLayer(String),
|
||||||
|
}
|
||||||
|
enum AnchorPosition {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BarState {
|
||||||
|
is_expanded: bool,
|
||||||
|
current_anchor: AnchorPosition,
|
||||||
|
current_layer: Layer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BarState {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
is_expanded: false,
|
||||||
|
current_anchor: AnchorPosition::Top,
|
||||||
|
current_layer: Layer::Top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn anchor_name(&self) -> &'static str {
|
||||||
|
match self.current_anchor {
|
||||||
|
AnchorPosition::Top => "Top",
|
||||||
|
AnchorPosition::Bottom => "Bottom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_anchor(&mut self) {
|
||||||
|
self.current_anchor = match self.current_anchor {
|
||||||
|
AnchorPosition::Top => AnchorPosition::Bottom,
|
||||||
|
AnchorPosition::Bottom => AnchorPosition::Top,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_anchor_edges(&self) -> AnchorEdges {
|
||||||
|
match self.current_anchor {
|
||||||
|
AnchorPosition::Top => AnchorEdges::top_bar(),
|
||||||
|
AnchorPosition::Bottom => AnchorEdges::bottom_bar(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layer_name(&self) -> &'static str {
|
||||||
|
match self.current_layer {
|
||||||
|
Layer::Background => "Background",
|
||||||
|
Layer::Bottom => "Bottom",
|
||||||
|
Layer::Top => "Top",
|
||||||
|
Layer::Overlay => "Overlay",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_layer(&mut self) -> Layer {
|
||||||
|
self.current_layer = match self.current_layer {
|
||||||
|
Layer::Background => Layer::Bottom,
|
||||||
|
Layer::Bottom => Layer::Top,
|
||||||
|
Layer::Top => Layer::Overlay,
|
||||||
|
Layer::Overlay => Layer::Background,
|
||||||
|
};
|
||||||
|
self.current_layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_toggle_size_callback(
|
||||||
|
sender: &Rc<Sender<UiUpdate>>,
|
||||||
|
shell: &Shell,
|
||||||
|
state: &Rc<RefCell<BarState>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let state_clone = Rc::clone(state);
|
||||||
|
let sender_clone = Rc::clone(sender);
|
||||||
|
shell.on("Bar", "toggle-size", move |control| {
|
||||||
|
let is_expanded = {
|
||||||
|
let mut st = state_clone.borrow_mut();
|
||||||
|
st.is_expanded = !st.is_expanded;
|
||||||
|
|
||||||
|
let new_size = if st.is_expanded { 64 } else { 32 };
|
||||||
|
|
||||||
|
let (width, height) = match st.current_anchor {
|
||||||
|
AnchorPosition::Top | AnchorPosition::Bottom => {
|
||||||
|
log::info!("Resizing horizontal bar to {}px", new_size);
|
||||||
|
(0, new_size)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bar = control.surface("Bar");
|
||||||
|
if let Err(e) = bar.resize(width, height) {
|
||||||
|
log::error!("Failed to resize bar: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = bar.set_exclusive_zone(new_size.try_into().unwrap_or(32)) {
|
||||||
|
log::error!("Failed to set exclusive zone: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = control.surface("Bar").set_margins((0, 0, 0, 0)) {
|
||||||
|
log::error!("Failed to set margins: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Updated bar state: size={}, is_expanded={}",
|
||||||
|
new_size,
|
||||||
|
st.is_expanded
|
||||||
|
);
|
||||||
|
|
||||||
|
st.is_expanded
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = sender_clone.send(UiUpdate::IsExpanded(is_expanded)) {
|
||||||
|
log::error!("Failed to send UI update: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = control.request_redraw() {
|
||||||
|
log::error!("Failed to request redraw: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Struct(Struct::from_iter([("expanded".into(), is_expanded.into())]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_anchor_switch_callback(
|
||||||
|
sender: &Rc<Sender<UiUpdate>>,
|
||||||
|
shell: &Shell,
|
||||||
|
state: &Rc<RefCell<BarState>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let state_clone = Rc::clone(state);
|
||||||
|
let sender_clone = Rc::clone(sender);
|
||||||
|
shell.on("Bar", "switch-anchor", move |control| {
|
||||||
|
let anchor_name = {
|
||||||
|
let mut st = state_clone.borrow_mut();
|
||||||
|
st.next_anchor();
|
||||||
|
|
||||||
|
log::info!("Switching to anchor: {}", st.anchor_name());
|
||||||
|
|
||||||
|
let bar = control.surface("Bar");
|
||||||
|
if let Err(e) = bar.set_anchor(st.get_anchor_edges()) {
|
||||||
|
log::error!("Failed to apply config: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
st.anchor_name()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = sender_clone.send(UiUpdate::CurrentAnchor(anchor_name.to_string())) {
|
||||||
|
log::error!("Failed to send UI update: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = control.request_redraw() {
|
||||||
|
log::error!("Failed to request redraw: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Switched to {} anchor", anchor_name);
|
||||||
|
|
||||||
|
Value::Struct(Struct::from_iter([(
|
||||||
|
"anchor".into(),
|
||||||
|
SharedString::from(anchor_name).into(),
|
||||||
|
)]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_layer_switch_callback(
|
||||||
|
sender: &Rc<Sender<UiUpdate>>,
|
||||||
|
shell: &Shell,
|
||||||
|
state: &Rc<RefCell<BarState>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let state_clone = Rc::clone(state);
|
||||||
|
let sender_clone = Rc::clone(sender);
|
||||||
|
shell.on("Bar", "switch-layer", move |control| {
|
||||||
|
let layer_name = {
|
||||||
|
let mut st = state_clone.borrow_mut();
|
||||||
|
let new_layer = st.next_layer();
|
||||||
|
|
||||||
|
log::info!("Switching to layer: {:?}", new_layer);
|
||||||
|
|
||||||
|
let bar = control.surface("Bar");
|
||||||
|
if let Err(e) = bar.set_layer(new_layer) {
|
||||||
|
log::error!("Failed to set layer: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
st.layer_name()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = sender_clone.send(UiUpdate::CurrentLayer(layer_name.to_string())) {
|
||||||
|
log::error!("Failed to send UI update: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Switched to {} layer", layer_name);
|
||||||
|
|
||||||
|
Value::Struct(Struct::from_iter([(
|
||||||
|
"layer".into(),
|
||||||
|
SharedString::from(layer_name).into(),
|
||||||
|
)]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
log::info!("Starting runtime-control example");
|
||||||
|
log::info!("This example demonstrates dynamic surface manipulation at runtime");
|
||||||
|
|
||||||
|
let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/bar.slint");
|
||||||
|
|
||||||
|
let state = Rc::new(RefCell::new(BarState::new()));
|
||||||
|
|
||||||
|
let mut shell = Shell::from_file(ui_path)
|
||||||
|
.surface("Bar")
|
||||||
|
.height(32)
|
||||||
|
.anchor(AnchorEdges::top_bar())
|
||||||
|
.exclusive_zone(32)
|
||||||
|
.namespace("runtime-control-example")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
shell.with_all_surfaces(|_name, component| {
|
||||||
|
log::info!("Initializing properties for Bar surface");
|
||||||
|
let state_ref = state.borrow();
|
||||||
|
|
||||||
|
let set_property = |name: &str, value: Value| {
|
||||||
|
if let Err(e) = component.set_property(name, value) {
|
||||||
|
log::error!("Failed to set initial {}: {}", name, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
set_property("is-expanded", state_ref.is_expanded.into());
|
||||||
|
set_property(
|
||||||
|
"current-anchor",
|
||||||
|
SharedString::from(state_ref.anchor_name()).into(),
|
||||||
|
);
|
||||||
|
set_property(
|
||||||
|
"current-layer",
|
||||||
|
SharedString::from(state_ref.layer_name()).into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
log::info!("Initialized properties for Bar surface");
|
||||||
|
});
|
||||||
|
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
let (_token, sender) = handle.add_channel(|message: UiUpdate, app_state| {
|
||||||
|
log::info!("Received UI update: {:?}", message);
|
||||||
|
|
||||||
|
for surface in app_state.all_outputs() {
|
||||||
|
let component = surface.component_instance();
|
||||||
|
|
||||||
|
match &message {
|
||||||
|
UiUpdate::IsExpanded(is_expanded) => {
|
||||||
|
if let Err(e) = component.set_property("is-expanded", (*is_expanded).into()) {
|
||||||
|
log::error!("Failed to set is-expanded: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiUpdate::CurrentAnchor(anchor) => {
|
||||||
|
if let Err(e) = component
|
||||||
|
.set_property("current-anchor", SharedString::from(anchor.as_str()).into())
|
||||||
|
{
|
||||||
|
log::error!("Failed to set current-anchor: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiUpdate::CurrentLayer(layer) => {
|
||||||
|
if let Err(e) = component
|
||||||
|
.set_property("current-layer", SharedString::from(layer.as_str()).into())
|
||||||
|
{
|
||||||
|
log::error!("Failed to set current-layer: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let sender_rc = Rc::new(sender);
|
||||||
|
|
||||||
|
setup_toggle_size_callback(&sender_rc, &shell, &state)?;
|
||||||
|
setup_anchor_switch_callback(&sender_rc, &shell, &state)?;
|
||||||
|
setup_layer_switch_callback(&sender_rc, &shell, &state)?;
|
||||||
|
shell.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
58
examples/runtime-surface-config/ui/bar.slint
Normal file
58
examples/runtime-surface-config/ui/bar.slint
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Runtime Control Example - Interactive Status Bar
|
||||||
|
|
||||||
|
import { Button } from "std-widgets.slint";
|
||||||
|
export component Bar inherits Window {
|
||||||
|
in-out property <bool> is-expanded: false;
|
||||||
|
in-out property <string> current-anchor: "Top";
|
||||||
|
in-out property <string> current-layer: "Top";
|
||||||
|
|
||||||
|
callback toggle-size() -> {expanded: bool};
|
||||||
|
callback switch-anchor() -> {anchor: string};
|
||||||
|
callback switch-layer() -> {layer: string};
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 12px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Click buttons to control surface";
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Anchor: " + current-anchor;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Layer: " + current-layer;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
horizontal-stretch: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: is-expanded ? "Collapse" : "Expand";
|
||||||
|
clicked => {
|
||||||
|
root.is-expanded = toggle-size().expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Switch Anchor";
|
||||||
|
clicked => {
|
||||||
|
root.current-anchor = switch-anchor().anchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Switch Layer";
|
||||||
|
clicked => {
|
||||||
|
root.current-layer = switch-layer().layer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue