mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-12-12 14:25:54 +00:00
refactor: unify single and multi windows logic
This commit is contained in:
parent
6c564d95b5
commit
2767a64137
10 changed files with 979 additions and 359 deletions
|
|
@ -1,228 +0,0 @@
|
|||
use crate::Result;
|
||||
use crate::system::SingleWindowShell;
|
||||
use layer_shika_adapters::platform::slint_interpreter::{CompilationResult, Compiler};
|
||||
use layer_shika_domain::errors::DomainError;
|
||||
use layer_shika_domain::prelude::{
|
||||
AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, WindowConfig,
|
||||
WindowDimension,
|
||||
};
|
||||
use spin_on::spin_on;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct NeedsComponent;
|
||||
pub struct HasComponent {
|
||||
component_name: String,
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
}
|
||||
|
||||
pub struct LayerShika<State> {
|
||||
state: State,
|
||||
config: WindowConfig,
|
||||
}
|
||||
|
||||
impl LayerShika<NeedsComponent> {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
component_name: impl Into<String>,
|
||||
) -> LayerShika<HasComponent> {
|
||||
LayerShika {
|
||||
state: HasComponent {
|
||||
component_name: component_name.into(),
|
||||
compilation_result,
|
||||
},
|
||||
config: WindowConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file(path: impl AsRef<Path>) -> Result<LayerShika<HasComponent>> {
|
||||
Self::from_file_with_component(path, "Main")
|
||||
}
|
||||
|
||||
pub fn from_file_with_component(
|
||||
path: impl AsRef<Path>,
|
||||
component_name: impl AsRef<str>,
|
||||
) -> Result<LayerShika<HasComponent>> {
|
||||
Self::from_file_with_compiler(path, &mut Compiler::default(), component_name.as_ref())
|
||||
}
|
||||
|
||||
pub fn from_file_with_compiler(
|
||||
path: impl AsRef<Path>,
|
||||
compiler: &mut Compiler,
|
||||
component_name: &str,
|
||||
) -> Result<LayerShika<HasComponent>> {
|
||||
let compilation_result = spin_on(compiler.build_from_path(path.as_ref()));
|
||||
let diagnostics: Vec<_> = compilation_result.diagnostics().collect();
|
||||
if !diagnostics.is_empty() {
|
||||
let messages: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
|
||||
return Err(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Failed to compile Slint file '{}':\n{}",
|
||||
path.as_ref().display(),
|
||||
messages.join("\n")
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
compilation_result
|
||||
.component(component_name)
|
||||
.ok_or_else(|| DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in Slint file '{}'",
|
||||
component_name,
|
||||
path.as_ref().display()
|
||||
),
|
||||
})?;
|
||||
|
||||
Ok(LayerShika {
|
||||
state: HasComponent {
|
||||
component_name: component_name.to_string(),
|
||||
compilation_result: Rc::new(compilation_result),
|
||||
},
|
||||
config: WindowConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_source(source: impl AsRef<str>) -> Result<LayerShika<HasComponent>> {
|
||||
Self::from_source_with_component(source, "Main")
|
||||
}
|
||||
|
||||
pub fn from_source_with_component(
|
||||
source: impl AsRef<str>,
|
||||
component_name: impl AsRef<str>,
|
||||
) -> Result<LayerShika<HasComponent>> {
|
||||
Self::from_source_with_compiler(source, &mut Compiler::default(), component_name.as_ref())
|
||||
}
|
||||
|
||||
pub fn from_source_with_compiler(
|
||||
source: impl AsRef<str>,
|
||||
compiler: &mut Compiler,
|
||||
component_name: &str,
|
||||
) -> Result<LayerShika<HasComponent>> {
|
||||
let compilation_result =
|
||||
spin_on(compiler.build_from_source(source.as_ref().to_string(), PathBuf::default()));
|
||||
|
||||
let diagnostics: Vec<_> = compilation_result.diagnostics().collect();
|
||||
if !diagnostics.is_empty() {
|
||||
let messages: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
|
||||
return Err(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Failed to compile Slint source code:\n{}",
|
||||
messages.join("\n")
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
compilation_result
|
||||
.component(component_name)
|
||||
.ok_or_else(|| DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in Slint source code",
|
||||
component_name
|
||||
),
|
||||
})?;
|
||||
|
||||
Ok(LayerShika {
|
||||
state: HasComponent {
|
||||
component_name: component_name.to_string(),
|
||||
compilation_result: Rc::new(compilation_result),
|
||||
},
|
||||
config: WindowConfig::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerShika<HasComponent> {
|
||||
#[must_use]
|
||||
pub fn size(mut self, width: u32, height: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(width, height);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn height(mut self, height: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(self.config.dimensions.width(), height);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn width(mut self, width: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(width, self.config.dimensions.height());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn layer(mut self, layer: Layer) -> Self {
|
||||
self.config.layer = layer;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn margin(mut self, margin: impl Into<Margins>) -> Self {
|
||||
self.config.margin = margin.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn anchor(mut self, anchor: AnchorEdges) -> Self {
|
||||
self.config.anchor = anchor;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn exclusive_zone(mut self, zone: i32) -> Self {
|
||||
self.config.exclusive_zone = zone;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
|
||||
self.config.namespace = namespace.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn scale_factor(mut self, sf: impl TryInto<ScaleFactor, Error = DomainError>) -> Self {
|
||||
self.config.scale_factor = sf.try_into().unwrap_or_default();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self {
|
||||
self.config.keyboard_interactivity = mode;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
|
||||
self.config.output_policy = policy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<SingleWindowShell> {
|
||||
let component_definition = self
|
||||
.state
|
||||
.compilation_result
|
||||
.component(&self.state.component_name)
|
||||
.ok_or_else(|| DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in compilation result",
|
||||
self.state.component_name
|
||||
),
|
||||
})?;
|
||||
|
||||
SingleWindowShell::new(
|
||||
component_definition,
|
||||
Some(self.state.compilation_result),
|
||||
self.config,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<()> {
|
||||
let mut app = self.build()?;
|
||||
app.run()
|
||||
}
|
||||
}
|
||||
897
crates/composition/src/layer_shika.rs
Normal file
897
crates/composition/src/layer_shika.rs
Normal file
|
|
@ -0,0 +1,897 @@
|
|||
use crate::event_loop::{EventLoopHandleBase, FromAppState};
|
||||
use crate::popup_builder::PopupBuilder;
|
||||
use crate::shell::LayerSurfaceHandle;
|
||||
use crate::shell_runtime::ShellRuntime;
|
||||
use crate::system::{PopupCommand, ShellControl};
|
||||
use crate::value_conversion::IntoValue;
|
||||
use crate::{Error, Result};
|
||||
use layer_shika_adapters::errors::EventLoopError;
|
||||
use layer_shika_adapters::platform::calloop::channel;
|
||||
use layer_shika_adapters::platform::slint_interpreter::{
|
||||
CompilationResult, Compiler, ComponentInstance, Value,
|
||||
};
|
||||
use layer_shika_adapters::{
|
||||
AppState, ShellWindowConfig, WaylandWindowConfig, WindowState, WindowingSystemFacade,
|
||||
};
|
||||
use layer_shika_domain::config::WindowConfig;
|
||||
use layer_shika_domain::entities::output_registry::OutputRegistry;
|
||||
use layer_shika_domain::errors::DomainError;
|
||||
use layer_shika_domain::prelude::{
|
||||
AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, WindowDimension,
|
||||
};
|
||||
use layer_shika_domain::value_objects::output_handle::OutputHandle;
|
||||
use layer_shika_domain::value_objects::output_info::OutputInfo;
|
||||
use spin_on::spin_on;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub const DEFAULT_COMPONENT_NAME: &str = "Main";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowDefinition {
|
||||
pub component: String,
|
||||
pub config: WindowConfig,
|
||||
}
|
||||
|
||||
enum CompilationSource {
|
||||
File { path: PathBuf, compiler: Compiler },
|
||||
Source { code: String, compiler: Compiler },
|
||||
Compiled(Rc<CompilationResult>),
|
||||
}
|
||||
|
||||
pub struct ShellBuilder {
|
||||
compilation: CompilationSource,
|
||||
windows: Vec<WindowDefinition>,
|
||||
}
|
||||
|
||||
impl ShellBuilder {
|
||||
pub fn window(self, component: impl Into<String>) -> WindowConfigBuilder {
|
||||
WindowConfigBuilder {
|
||||
shell_builder: self,
|
||||
component: component.into(),
|
||||
config: WindowConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn discover_windows(
|
||||
mut self,
|
||||
components: impl IntoIterator<Item = impl Into<String>>,
|
||||
) -> Self {
|
||||
for component in components {
|
||||
self.windows.push(WindowDefinition {
|
||||
component: component.into(),
|
||||
config: WindowConfig::default(),
|
||||
});
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Runtime> {
|
||||
let windows = if self.windows.is_empty() {
|
||||
vec![WindowDefinition {
|
||||
component: DEFAULT_COMPONENT_NAME.to_string(),
|
||||
config: WindowConfig::default(),
|
||||
}]
|
||||
} else {
|
||||
self.windows
|
||||
};
|
||||
|
||||
let compilation_result = match self.compilation {
|
||||
CompilationSource::File { path, compiler } => {
|
||||
let result = spin_on(compiler.build_from_path(&path));
|
||||
let diagnostics: Vec<_> = result.diagnostics().collect();
|
||||
if !diagnostics.is_empty() {
|
||||
let messages: Vec<String> =
|
||||
diagnostics.iter().map(ToString::to_string).collect();
|
||||
return Err(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Failed to compile Slint file '{}':\n{}",
|
||||
path.display(),
|
||||
messages.join("\n")
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Rc::new(result)
|
||||
}
|
||||
CompilationSource::Source { code, compiler } => {
|
||||
let result = spin_on(compiler.build_from_source(code, PathBuf::default()));
|
||||
let diagnostics: Vec<_> = result.diagnostics().collect();
|
||||
if !diagnostics.is_empty() {
|
||||
let messages: Vec<String> =
|
||||
diagnostics.iter().map(ToString::to_string).collect();
|
||||
return Err(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Failed to compile Slint source:\n{}",
|
||||
messages.join("\n")
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Rc::new(result)
|
||||
}
|
||||
CompilationSource::Compiled(result) => result,
|
||||
};
|
||||
|
||||
Runtime::new(compilation_result, windows)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowConfigBuilder {
|
||||
shell_builder: ShellBuilder,
|
||||
component: String,
|
||||
config: WindowConfig,
|
||||
}
|
||||
|
||||
impl WindowConfigBuilder {
|
||||
#[must_use]
|
||||
pub fn size(mut self, width: u32, height: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(width, height);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn height(mut self, height: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(self.config.dimensions.width(), height);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn width(mut self, width: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(width, self.config.dimensions.height());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn layer(mut self, layer: Layer) -> Self {
|
||||
self.config.layer = layer;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn margin(mut self, margin: impl Into<Margins>) -> Self {
|
||||
self.config.margin = margin.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn anchor(mut self, anchor: AnchorEdges) -> Self {
|
||||
self.config.anchor = anchor;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn exclusive_zone(mut self, zone: i32) -> Self {
|
||||
self.config.exclusive_zone = zone;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
|
||||
self.config.namespace = namespace.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn scale_factor(mut self, sf: impl TryInto<ScaleFactor, Error = DomainError>) -> Self {
|
||||
self.config.scale_factor = sf.try_into().unwrap_or_default();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self {
|
||||
self.config.keyboard_interactivity = mode;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
|
||||
self.config.output_policy = policy;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn window(self, component: impl Into<String>) -> WindowConfigBuilder {
|
||||
let shell_builder = self.complete();
|
||||
shell_builder.window(component)
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Runtime> {
|
||||
self.complete().build()
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<()> {
|
||||
let mut runtime = self.build()?;
|
||||
runtime.run()
|
||||
}
|
||||
|
||||
fn complete(mut self) -> ShellBuilder {
|
||||
self.shell_builder.windows.push(WindowDefinition {
|
||||
component: self.component,
|
||||
config: self.config,
|
||||
});
|
||||
self.shell_builder
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayerShika;
|
||||
|
||||
impl LayerShika {
|
||||
pub fn from_file(path: impl AsRef<Path>) -> ShellBuilder {
|
||||
ShellBuilder {
|
||||
compilation: CompilationSource::File {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
compiler: Compiler::default(),
|
||||
},
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file_with_compiler(path: impl AsRef<Path>, compiler: Compiler) -> ShellBuilder {
|
||||
ShellBuilder {
|
||||
compilation: CompilationSource::File {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
compiler,
|
||||
},
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_source(code: impl Into<String>) -> ShellBuilder {
|
||||
ShellBuilder {
|
||||
compilation: CompilationSource::Source {
|
||||
code: code.into(),
|
||||
compiler: Compiler::default(),
|
||||
},
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_source_with_compiler(code: impl Into<String>, compiler: Compiler) -> ShellBuilder {
|
||||
ShellBuilder {
|
||||
compilation: CompilationSource::Source {
|
||||
code: code.into(),
|
||||
compiler,
|
||||
},
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_compilation(result: Rc<CompilationResult>) -> ShellBuilder {
|
||||
ShellBuilder {
|
||||
compilation: CompilationSource::Compiled(result),
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_file(path: impl AsRef<Path>) -> Result<Rc<CompilationResult>> {
|
||||
let compiler = Compiler::default();
|
||||
let result = spin_on(compiler.build_from_path(path.as_ref()));
|
||||
let diagnostics: Vec<_> = result.diagnostics().collect();
|
||||
if !diagnostics.is_empty() {
|
||||
let messages: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
|
||||
return Err(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Failed to compile Slint file '{}':\n{}",
|
||||
path.as_ref().display(),
|
||||
messages.join("\n")
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Ok(Rc::new(result))
|
||||
}
|
||||
|
||||
pub fn compile_source(code: impl Into<String>) -> Result<Rc<CompilationResult>> {
|
||||
let compiler = Compiler::default();
|
||||
let result = spin_on(compiler.build_from_source(code.into(), PathBuf::default()));
|
||||
let diagnostics: Vec<_> = result.diagnostics().collect();
|
||||
if !diagnostics.is_empty() {
|
||||
let messages: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
|
||||
return Err(DomainError::Configuration {
|
||||
message: format!("Failed to compile Slint source:\n{}", messages.join("\n")),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Ok(Rc::new(result))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Runtime {
|
||||
inner: Rc<RefCell<WindowingSystemFacade>>,
|
||||
windows: HashMap<String, WindowDefinition>,
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
popup_command_sender: channel::Sender<PopupCommand>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub(crate) fn new(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
definitions: Vec<WindowDefinition>,
|
||||
) -> Result<Self> {
|
||||
log::info!(
|
||||
"Creating LayerShika runtime with {} windows",
|
||||
definitions.len()
|
||||
);
|
||||
|
||||
if definitions.is_empty() {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: "At least one window definition is required".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
let is_single_window = definitions.len() == 1;
|
||||
|
||||
if is_single_window {
|
||||
let definition = definitions.into_iter().next().ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: "Expected at least one window definition".to_string(),
|
||||
})
|
||||
})?;
|
||||
Self::new_single_window(compilation_result, definition)
|
||||
} else {
|
||||
Self::new_multi_window(compilation_result, definitions)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_single_window(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
definition: WindowDefinition,
|
||||
) -> Result<Self> {
|
||||
let component_definition = compilation_result
|
||||
.component(&definition.component)
|
||||
.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in compilation result",
|
||||
definition.component
|
||||
),
|
||||
})
|
||||
})?;
|
||||
|
||||
let wayland_config = WaylandWindowConfig::from_domain_config(
|
||||
component_definition,
|
||||
Some(Rc::clone(&compilation_result)),
|
||||
definition.config.clone(),
|
||||
);
|
||||
|
||||
let inner = layer_shika_adapters::WaylandWindowingSystem::new(&wayland_config)?;
|
||||
let facade = WindowingSystemFacade::new(inner);
|
||||
let inner_rc = Rc::new(RefCell::new(facade));
|
||||
|
||||
let (sender, receiver) = channel::channel();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
windows.insert(definition.component.clone(), definition);
|
||||
|
||||
let shell = Self {
|
||||
inner: Rc::clone(&inner_rc),
|
||||
windows,
|
||||
compilation_result,
|
||||
popup_command_sender: sender,
|
||||
};
|
||||
|
||||
shell.setup_popup_command_handler(receiver)?;
|
||||
|
||||
log::info!("LayerShika runtime created (single-window mode)");
|
||||
|
||||
Ok(shell)
|
||||
}
|
||||
|
||||
fn new_multi_window(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
definitions: Vec<WindowDefinition>,
|
||||
) -> Result<Self> {
|
||||
let shell_configs: Vec<ShellWindowConfig> = definitions
|
||||
.iter()
|
||||
.map(|def| {
|
||||
let component_definition = compilation_result
|
||||
.component(&def.component)
|
||||
.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in compilation result",
|
||||
def.component
|
||||
),
|
||||
})
|
||||
})?;
|
||||
|
||||
let wayland_config = WaylandWindowConfig::from_domain_config(
|
||||
component_definition,
|
||||
Some(Rc::clone(&compilation_result)),
|
||||
def.config.clone(),
|
||||
);
|
||||
|
||||
Ok(ShellWindowConfig {
|
||||
name: def.component.clone(),
|
||||
config: wayland_config,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let inner = layer_shika_adapters::WaylandWindowingSystem::new_multi(&shell_configs)?;
|
||||
let facade = WindowingSystemFacade::new(inner);
|
||||
let inner_rc = Rc::new(RefCell::new(facade));
|
||||
|
||||
let (sender, receiver) = channel::channel();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for definition in definitions {
|
||||
windows.insert(definition.component.clone(), definition);
|
||||
}
|
||||
|
||||
let shell = Self {
|
||||
inner: Rc::clone(&inner_rc),
|
||||
windows,
|
||||
compilation_result,
|
||||
popup_command_sender: sender,
|
||||
};
|
||||
|
||||
shell.setup_popup_command_handler(receiver)?;
|
||||
|
||||
log::info!(
|
||||
"LayerShika runtime created (multi-window mode) with windows: {:?}",
|
||||
shell.window_names()
|
||||
);
|
||||
|
||||
Ok(shell)
|
||||
}
|
||||
|
||||
fn setup_popup_command_handler(&self, receiver: channel::Channel<PopupCommand>) -> Result<()> {
|
||||
let loop_handle = self.inner.borrow().inner_ref().event_loop_handle();
|
||||
let control = self.control();
|
||||
|
||||
loop_handle
|
||||
.insert_source(receiver, move |event, (), app_state| {
|
||||
if let channel::Event::Msg(command) = event {
|
||||
let mut ctx = crate::system::EventContext::from_app_state(app_state);
|
||||
|
||||
match command {
|
||||
PopupCommand::Show(request) => {
|
||||
if let Err(e) = ctx.show_popup(&request, Some(control.clone())) {
|
||||
log::error!("Failed to show popup: {}", e);
|
||||
}
|
||||
}
|
||||
PopupCommand::Close(handle) => {
|
||||
if let Err(e) = ctx.close_popup(handle) {
|
||||
log::error!("Failed to close popup: {}", e);
|
||||
}
|
||||
}
|
||||
PopupCommand::Resize {
|
||||
handle,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
if let Err(e) = ctx.resize_popup(handle, width, height) {
|
||||
log::error!("Failed to resize popup: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
Error::Adapter(
|
||||
EventLoopError::InsertSource {
|
||||
message: format!("Failed to setup popup command handler: {e:?}"),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn control(&self) -> ShellControl {
|
||||
ShellControl::new(self.popup_command_sender.clone())
|
||||
}
|
||||
|
||||
pub fn window_names(&self) -> Vec<&str> {
|
||||
self.windows.keys().map(String::as_str).collect()
|
||||
}
|
||||
|
||||
pub fn has_window(&self, name: &str) -> bool {
|
||||
self.windows.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn event_loop_handle(&self) -> EventLoopHandle {
|
||||
EventLoopHandle::new(Rc::downgrade(&self.inner))
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
log::info!(
|
||||
"Starting LayerShika event loop with {} windows",
|
||||
self.windows.len()
|
||||
);
|
||||
self.inner.borrow_mut().run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn with_window<F, R>(&self, name: &str, f: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&ComponentInstance) -> R,
|
||||
{
|
||||
if !self.windows.contains_key(name) {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: format!("Window '{}' not found", name),
|
||||
}));
|
||||
}
|
||||
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
system
|
||||
.app_state()
|
||||
.windows_by_shell_name(name)
|
||||
.next()
|
||||
.map(|window| f(window.component_instance()))
|
||||
.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!("No instance found for window '{}'", name),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_all_windows<F>(&self, mut f: F)
|
||||
where
|
||||
F: FnMut(&str, &ComponentInstance),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for name in self.windows.keys() {
|
||||
for window in system.app_state().windows_by_shell_name(name) {
|
||||
f(name, window.component_instance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_output<F, R>(&self, handle: OutputHandle, f: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&ComponentInstance) -> R,
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
let window = system
|
||||
.app_state()
|
||||
.get_output_by_handle(handle)
|
||||
.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!("Output with handle {:?} not found", handle),
|
||||
})
|
||||
})?;
|
||||
Ok(f(window.component_instance()))
|
||||
}
|
||||
|
||||
pub fn with_all_outputs<F>(&self, mut f: F)
|
||||
where
|
||||
F: FnMut(OutputHandle, &ComponentInstance),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
for (handle, window) in system.app_state().outputs_with_handles() {
|
||||
f(handle, window.component_instance());
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compilation_result(&self) -> &Rc<CompilationResult> {
|
||||
&self.compilation_result
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn popup(&self, component_name: impl Into<String>) -> PopupBuilder<'_> {
|
||||
PopupBuilder::new(self, component_name.into())
|
||||
}
|
||||
|
||||
pub fn on<F, R>(&self, window_name: &str, callback_name: &str, handler: F) -> Result<()>
|
||||
where
|
||||
F: Fn(ShellControl) -> R + 'static,
|
||||
R: IntoValue,
|
||||
{
|
||||
if !self.windows.contains_key(window_name) {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: format!("Window '{}' not found", window_name),
|
||||
}));
|
||||
}
|
||||
|
||||
let control = self.control();
|
||||
let handler = Rc::new(handler);
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for window in system.app_state().windows_by_shell_name(window_name) {
|
||||
let handler_rc = Rc::clone(&handler);
|
||||
let control_clone = control.clone();
|
||||
if let Err(e) = window
|
||||
.component_instance()
|
||||
.set_callback(callback_name, move |_args| {
|
||||
handler_rc(control_clone.clone()).into_value()
|
||||
})
|
||||
{
|
||||
log::error!(
|
||||
"Failed to register callback '{}' on window '{}': {}",
|
||||
callback_name,
|
||||
window_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_with_args<F, R>(
|
||||
&self,
|
||||
window_name: &str,
|
||||
callback_name: &str,
|
||||
handler: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Fn(&[Value], ShellControl) -> R + 'static,
|
||||
R: IntoValue,
|
||||
{
|
||||
if !self.windows.contains_key(window_name) {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: format!("Window '{}' not found", window_name),
|
||||
}));
|
||||
}
|
||||
|
||||
let control = self.control();
|
||||
let handler = Rc::new(handler);
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for window in system.app_state().windows_by_shell_name(window_name) {
|
||||
let handler_rc = Rc::clone(&handler);
|
||||
let control_clone = control.clone();
|
||||
if let Err(e) = window
|
||||
.component_instance()
|
||||
.set_callback(callback_name, move |args| {
|
||||
handler_rc(args, control_clone.clone()).into_value()
|
||||
})
|
||||
{
|
||||
log::error!(
|
||||
"Failed to register callback '{}' on window '{}': {}",
|
||||
callback_name,
|
||||
window_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_global<F, R>(&self, callback_name: &str, handler: F) -> Result<()>
|
||||
where
|
||||
F: Fn(ShellControl) -> R + 'static,
|
||||
R: IntoValue,
|
||||
{
|
||||
let control = self.control();
|
||||
let handler = Rc::new(handler);
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for window in system.app_state().all_outputs() {
|
||||
let handler_rc = Rc::clone(&handler);
|
||||
let control_clone = control.clone();
|
||||
if let Err(e) = window
|
||||
.component_instance()
|
||||
.set_callback(callback_name, move |_args| {
|
||||
handler_rc(control_clone.clone()).into_value()
|
||||
})
|
||||
{
|
||||
log::error!(
|
||||
"Failed to register global callback '{}': {}",
|
||||
callback_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_global_with_args<F, R>(&self, callback_name: &str, handler: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&[Value], ShellControl) -> R + 'static,
|
||||
R: IntoValue,
|
||||
{
|
||||
let control = self.control();
|
||||
let handler = Rc::new(handler);
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for window in system.app_state().all_outputs() {
|
||||
let handler_rc = Rc::clone(&handler);
|
||||
let control_clone = control.clone();
|
||||
if let Err(e) = window
|
||||
.component_instance()
|
||||
.set_callback(callback_name, move |args| {
|
||||
handler_rc(args, control_clone.clone()).into_value()
|
||||
})
|
||||
{
|
||||
log::error!(
|
||||
"Failed to register global callback '{}': {}",
|
||||
callback_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_window_config<F>(&self, window_name: &str, f: F)
|
||||
where
|
||||
F: Fn(&ComponentInstance, LayerSurfaceHandle<'_>),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
if self.windows.contains_key(window_name) {
|
||||
for window in system.app_state().windows_by_shell_name(window_name) {
|
||||
let surface_handle = LayerSurfaceHandle::from_window_state(window);
|
||||
f(window.component_instance(), surface_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_global_config<F>(&self, f: F)
|
||||
where
|
||||
F: Fn(&ComponentInstance, LayerSurfaceHandle<'_>),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for window in system.app_state().all_outputs() {
|
||||
let surface_handle = LayerSurfaceHandle::from_window_state(window);
|
||||
f(window.component_instance(), surface_handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_registry(&self) -> OutputRegistry {
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
system.app_state().output_registry().clone()
|
||||
}
|
||||
|
||||
pub fn get_output_info(&self, handle: OutputHandle) -> Option<OutputInfo> {
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
system.app_state().get_output_info(handle).cloned()
|
||||
}
|
||||
|
||||
pub fn all_output_info(&self) -> Vec<OutputInfo> {
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
system.app_state().all_output_info().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellRuntime for Runtime {
|
||||
type LoopHandle = EventLoopHandle;
|
||||
type Context<'a> = EventContext<'a>;
|
||||
|
||||
fn event_loop_handle(&self) -> Self::LoopHandle {
|
||||
EventLoopHandle::new(Rc::downgrade(&self.inner))
|
||||
}
|
||||
|
||||
fn with_component<F>(&self, name: &str, mut f: F)
|
||||
where
|
||||
F: FnMut(&ComponentInstance),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
if self.windows.contains_key(name) {
|
||||
for window in system.app_state().windows_by_shell_name(name) {
|
||||
f(window.component_instance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_all_components<F>(&self, mut f: F)
|
||||
where
|
||||
F: FnMut(&str, &ComponentInstance),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for name in self.windows.keys() {
|
||||
for window in system.app_state().windows_by_shell_name(name) {
|
||||
f(name, window.component_instance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
self.inner.borrow_mut().run()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type EventLoopHandle = EventLoopHandleBase<EventContext<'static>>;
|
||||
|
||||
pub struct EventContext<'a> {
|
||||
app_state: &'a mut AppState,
|
||||
}
|
||||
|
||||
impl<'a> FromAppState<'a> for EventContext<'a> {
|
||||
fn from_app_state(app_state: &'a mut AppState) -> Self {
|
||||
Self { app_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventContext<'_> {
|
||||
pub fn get_window_component(&self, name: &str) -> Option<&ComponentInstance> {
|
||||
self.app_state
|
||||
.windows_by_shell_name(name)
|
||||
.next()
|
||||
.map(WindowState::component_instance)
|
||||
}
|
||||
|
||||
pub fn all_window_components(&self) -> impl Iterator<Item = &ComponentInstance> {
|
||||
self.app_state
|
||||
.all_outputs()
|
||||
.map(WindowState::component_instance)
|
||||
}
|
||||
|
||||
pub fn render_frame_if_dirty(&mut self) -> Result<()> {
|
||||
for window in self.app_state.all_outputs() {
|
||||
window.render_frame_if_dirty()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn primary_output_handle(&self) -> Option<OutputHandle> {
|
||||
self.app_state.primary_output_handle()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn active_output_handle(&self) -> Option<OutputHandle> {
|
||||
self.app_state.active_output_handle()
|
||||
}
|
||||
|
||||
pub fn output_registry(&self) -> &OutputRegistry {
|
||||
self.app_state.output_registry()
|
||||
}
|
||||
|
||||
pub fn outputs(&self) -> impl Iterator<Item = (OutputHandle, &ComponentInstance)> {
|
||||
self.app_state
|
||||
.outputs_with_handles()
|
||||
.map(|(handle, window)| (handle, window.component_instance()))
|
||||
}
|
||||
|
||||
pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> {
|
||||
self.app_state
|
||||
.get_output_by_handle(handle)
|
||||
.map(WindowState::component_instance)
|
||||
}
|
||||
|
||||
pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> {
|
||||
self.app_state.get_output_info(handle)
|
||||
}
|
||||
|
||||
pub fn all_output_info(&self) -> impl Iterator<Item = &OutputInfo> {
|
||||
self.app_state.all_output_info()
|
||||
}
|
||||
|
||||
pub fn outputs_with_info(&self) -> impl Iterator<Item = (&OutputInfo, &ComponentInstance)> {
|
||||
self.app_state
|
||||
.outputs_with_info()
|
||||
.map(|(info, window)| (info, window.component_instance()))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
|
||||
self.app_state
|
||||
.primary_output()
|
||||
.and_then(WindowState::compilation_result)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
#![allow(clippy::pub_use)]
|
||||
|
||||
mod builder;
|
||||
mod event_loop;
|
||||
mod layer_shika;
|
||||
mod popup_builder;
|
||||
mod shell;
|
||||
mod shell_composition;
|
||||
mod shell_runtime;
|
||||
mod system;
|
||||
pub mod value_conversion;
|
||||
|
|
@ -13,7 +12,6 @@ use layer_shika_adapters::errors::LayerShikaError;
|
|||
use layer_shika_domain::errors::DomainError;
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
pub use builder::LayerShika;
|
||||
pub use layer_shika_adapters::PopupWindow;
|
||||
pub use layer_shika_adapters::platform::{slint, slint_interpreter};
|
||||
pub use layer_shika_domain::entities::output_registry::OutputRegistry;
|
||||
|
|
@ -37,7 +35,12 @@ pub use shell::{
|
|||
LayerSurfaceHandle, Shell, ShellEventContext, ShellEventLoopHandle, ShellWindowConfigHandler,
|
||||
ShellWindowHandle,
|
||||
};
|
||||
pub use shell_composition::{ShellComposition, ShellWindowDefinition};
|
||||
|
||||
pub use layer_shika::{
|
||||
DEFAULT_COMPONENT_NAME, EventContext as LayerShikaEventContext,
|
||||
EventLoopHandle as LayerShikaEventLoopHandle, LayerShika, Runtime, ShellBuilder,
|
||||
WindowConfigBuilder, WindowDefinition,
|
||||
};
|
||||
|
||||
pub mod calloop {
|
||||
pub use layer_shika_adapters::platform::calloop::{
|
||||
|
|
@ -61,16 +64,17 @@ pub enum Error {
|
|||
|
||||
pub mod prelude {
|
||||
pub use crate::{
|
||||
AnchorEdges, AnchorStrategy, DEFAULT_WINDOW_NAME, EventContext, EventLoopHandle, IntoValue,
|
||||
KeyboardInteractivity, Layer, LayerShika, OutputGeometry, OutputHandle, OutputInfo,
|
||||
OutputPolicy, OutputRegistry, PopupBuilder, PopupHandle, PopupPlacement,
|
||||
PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, ShellControl,
|
||||
ShellRuntime, SingleWindowShell,
|
||||
AnchorEdges, AnchorStrategy, DEFAULT_COMPONENT_NAME, DEFAULT_WINDOW_NAME, EventContext,
|
||||
EventLoopHandle, IntoValue, KeyboardInteractivity, Layer, LayerShika,
|
||||
LayerShikaEventContext, LayerShikaEventLoopHandle, OutputGeometry, OutputHandle,
|
||||
OutputInfo, OutputPolicy, OutputRegistry, PopupBuilder, PopupHandle, PopupPlacement,
|
||||
PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, Runtime, ShellBuilder,
|
||||
ShellControl, ShellRuntime, SingleWindowShell, WindowConfigBuilder, WindowDefinition,
|
||||
};
|
||||
|
||||
pub use crate::{
|
||||
LayerSurfaceHandle, Shell, ShellComposition, ShellEventContext, ShellEventLoopHandle,
|
||||
ShellWindowConfigHandler, ShellWindowDefinition, ShellWindowHandle,
|
||||
LayerSurfaceHandle, Shell, ShellEventContext, ShellEventLoopHandle,
|
||||
ShellWindowConfigHandler, ShellWindowHandle,
|
||||
};
|
||||
|
||||
pub use crate::calloop::{Generic, Interest, Mode, PostAction, RegistrationToken, Timer};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use crate::Result;
|
||||
use crate::system::SingleWindowShell;
|
||||
use crate::layer_shika::Runtime;
|
||||
use layer_shika_adapters::platform::slint_interpreter::Value;
|
||||
use layer_shika_domain::prelude::AnchorStrategy;
|
||||
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
|
||||
use layer_shika_domain::value_objects::popup_request::{PopupPlacement, PopupRequest, PopupSize};
|
||||
|
||||
pub struct PopupBuilder<'a> {
|
||||
shell: &'a SingleWindowShell,
|
||||
shell: &'a Runtime,
|
||||
component: String,
|
||||
reference: PopupPlacement,
|
||||
anchor: PopupPositioningMode,
|
||||
|
|
@ -17,7 +17,7 @@ pub struct PopupBuilder<'a> {
|
|||
}
|
||||
|
||||
impl<'a> PopupBuilder<'a> {
|
||||
pub(crate) fn new(shell: &'a SingleWindowShell, component: String) -> Self {
|
||||
pub(crate) fn new(shell: &'a Runtime, component: String) -> Self {
|
||||
Self {
|
||||
shell,
|
||||
component,
|
||||
|
|
@ -148,7 +148,7 @@ impl<'a> PopupBuilder<'a> {
|
|||
let request = self.build_request();
|
||||
let control = self.shell.control();
|
||||
|
||||
self.shell.with_all_component_instances(|instance| {
|
||||
self.shell.with_all_windows(|_name, instance| {
|
||||
let request_clone = request.clone();
|
||||
let control_clone = control.clone();
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ impl<'a> PopupBuilder<'a> {
|
|||
let control = self.shell.control();
|
||||
let component_name = request.component.clone();
|
||||
|
||||
self.shell.with_all_component_instances(|instance| {
|
||||
self.shell.with_all_windows(|_name, instance| {
|
||||
let request_clone = request.clone();
|
||||
let control_clone = control.clone();
|
||||
let component_clone = component_name.clone();
|
||||
|
|
@ -205,7 +205,7 @@ impl<'a> PopupBuilder<'a> {
|
|||
let resize_callback = self.resize_callback.clone();
|
||||
let control = self.shell.control();
|
||||
|
||||
self.shell.with_all_component_instances(|instance| {
|
||||
self.shell.with_all_windows(|_name, instance| {
|
||||
let component_clone = component_name.clone();
|
||||
let control_clone = control.clone();
|
||||
let close_cb = close_callback.clone();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::event_loop::{EventLoopHandleBase, FromAppState};
|
||||
use crate::shell_composition::ShellWindowDefinition;
|
||||
use crate::layer_shika::WindowDefinition;
|
||||
use crate::shell_runtime::ShellRuntime;
|
||||
use crate::system::{EventContext, PopupCommand, ShellControl};
|
||||
use crate::value_conversion::IntoValue;
|
||||
|
|
@ -29,7 +29,11 @@ pub struct LayerSurfaceHandle<'a> {
|
|||
window_state: &'a WindowState,
|
||||
}
|
||||
|
||||
impl LayerSurfaceHandle<'_> {
|
||||
impl<'a> LayerSurfaceHandle<'a> {
|
||||
pub(crate) fn from_window_state(window_state: &'a WindowState) -> Self {
|
||||
Self { window_state }
|
||||
}
|
||||
|
||||
pub fn set_anchor(&self, anchor: Anchor) {
|
||||
self.window_state.layer_surface().set_anchor(anchor);
|
||||
}
|
||||
|
|
@ -102,10 +106,11 @@ pub struct Shell {
|
|||
popup_command_sender: channel::Sender<PopupCommand>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Shell {
|
||||
pub(crate) fn new(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
definitions: &[ShellWindowDefinition],
|
||||
definitions: &[WindowDefinition],
|
||||
) -> Result<Self> {
|
||||
log::info!("Creating shell with {} windows", definitions.len());
|
||||
|
||||
|
|
@ -119,12 +124,12 @@ impl Shell {
|
|||
.iter()
|
||||
.map(|def| {
|
||||
let component_definition = compilation_result
|
||||
.component(&def.component_name)
|
||||
.component(&def.component)
|
||||
.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in compilation result",
|
||||
def.component_name
|
||||
def.component
|
||||
),
|
||||
})
|
||||
})?;
|
||||
|
|
@ -136,7 +141,7 @@ impl Shell {
|
|||
);
|
||||
|
||||
Ok(ShellWindowConfig {
|
||||
name: def.component_name.clone(),
|
||||
name: def.component.clone(),
|
||||
config: wayland_config,
|
||||
})
|
||||
})
|
||||
|
|
@ -151,9 +156,9 @@ impl Shell {
|
|||
let mut windows = HashMap::new();
|
||||
for def in definitions {
|
||||
windows.insert(
|
||||
def.component_name.clone(),
|
||||
def.component.clone(),
|
||||
ShellWindowHandle {
|
||||
name: def.component_name.clone(),
|
||||
name: def.component.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
use crate::shell::Shell;
|
||||
use crate::{Error, Result};
|
||||
use layer_shika_adapters::platform::slint_interpreter::CompilationResult;
|
||||
use layer_shika_domain::config::WindowConfig;
|
||||
use layer_shika_domain::errors::DomainError;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShellWindowDefinition {
|
||||
pub component_name: String,
|
||||
pub config: WindowConfig,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct ShellComposition {
|
||||
compilation_result: Option<Rc<CompilationResult>>,
|
||||
shell_windows: Vec<ShellWindowDefinition>,
|
||||
auto_discover_components: Vec<String>,
|
||||
}
|
||||
|
||||
impl ShellComposition {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
compilation_result: None,
|
||||
shell_windows: Vec::new(),
|
||||
auto_discover_components: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_compilation_result(mut self, result: Rc<CompilationResult>) -> Self {
|
||||
self.compilation_result = Some(result);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_window(mut self, component_name: impl Into<String>, config: WindowConfig) -> Self {
|
||||
self.shell_windows.push(ShellWindowDefinition {
|
||||
component_name: component_name.into(),
|
||||
config,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_windows(mut self, definitions: Vec<ShellWindowDefinition>) -> Self {
|
||||
self.shell_windows.extend(definitions);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_default_config_for(mut self, components: Vec<impl Into<String>>) -> Self {
|
||||
self.auto_discover_components = components.into_iter().map(Into::into).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Shell> {
|
||||
let compilation_result = self.compilation_result.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: "No compilation result provided. Use with_compilation_result()"
|
||||
.to_string(),
|
||||
})
|
||||
})?;
|
||||
|
||||
if !self.auto_discover_components.is_empty() {
|
||||
return Shell::new_auto_discover(compilation_result, &self.auto_discover_components);
|
||||
}
|
||||
|
||||
if self.shell_windows.is_empty() {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: "No shell windows registered. Use add_window(), add_windows(), or with_default_config_for()".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
Shell::new(compilation_result, &self.shell_windows)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ShellComposition {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::event_loop::{EventLoopHandleBase, FromAppState};
|
||||
use crate::popup_builder::PopupBuilder;
|
||||
use crate::shell_runtime::{DEFAULT_WINDOW_NAME, ShellRuntime};
|
||||
use crate::value_conversion::IntoValue;
|
||||
use crate::{Error, Result};
|
||||
|
|
@ -559,6 +558,7 @@ pub struct SingleWindowShell {
|
|||
window_name: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl SingleWindowShell {
|
||||
pub(crate) fn new(
|
||||
component_definition: ComponentDefinition,
|
||||
|
|
@ -756,11 +756,6 @@ impl SingleWindowShell {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn popup(&self, component_name: impl Into<String>) -> PopupBuilder<'_> {
|
||||
PopupBuilder::new(self, component_name.into())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
self.inner.borrow_mut().run()?;
|
||||
Ok(())
|
||||
|
|
|
|||
58
src/lib.rs
58
src/lib.rs
|
|
@ -30,35 +30,58 @@
|
|||
//!
|
||||
//! # Quick Start
|
||||
//!
|
||||
//! Single-window use case:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use layer_shika::prelude::*;
|
||||
//!
|
||||
//! LayerShika::from_file("ui/main.slint")?
|
||||
//! .height(42)
|
||||
//! .anchor(AnchorEdges::top_bar())
|
||||
//! .exclusive_zone(42)
|
||||
//! LayerShika::from_file("ui/bar.slint")
|
||||
//! .window("Main")
|
||||
//! .height(42)
|
||||
//! .anchor(AnchorEdges::top_bar())
|
||||
//! .exclusive_zone(42)
|
||||
//! .build()?
|
||||
//! .run()?;
|
||||
//! # Ok::<(), layer_shika::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! # Multi-Window Shell
|
||||
//!
|
||||
//! For multi-window shell applications:
|
||||
//! Same API naturally extends to multiple windows:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use layer_shika::prelude::*;
|
||||
//! use std::rc::Rc;
|
||||
//!
|
||||
//! // Load Slint file with multiple shell window components
|
||||
//! let compilation_result = Rc::new(/* ... */);
|
||||
//! LayerShika::from_file("ui/shell.slint")
|
||||
//! .window("TopBar")
|
||||
//! .height(42)
|
||||
//! .anchor(AnchorEdges::top_bar())
|
||||
//! .window("Dock")
|
||||
//! .height(64)
|
||||
//! .anchor(AnchorEdges::bottom_bar())
|
||||
//! .build()?
|
||||
//! .run()?;
|
||||
//! # Ok::<(), layer_shika::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! // Create shell with typed WindowConfig
|
||||
//! let shell = ShellComposition::new()
|
||||
//! .with_compilation_result(compilation_result)
|
||||
//! .with_window("TopBar", WindowConfig::default())
|
||||
//! .build()?;
|
||||
//! # Pre-compiled Slint
|
||||
//!
|
||||
//! shell.run()?;
|
||||
//! For explicit compilation control:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use layer_shika::prelude::*;
|
||||
//!
|
||||
//! let compilation = LayerShika::compile_file("ui/shell.slint")?;
|
||||
//!
|
||||
//! LayerShika::from_compilation(compilation)
|
||||
//! .window("TopBar")
|
||||
//! .output_policy(OutputPolicy::AllOutputs)
|
||||
//! .height(42)
|
||||
//! .window("Dock")
|
||||
//! .output_policy(OutputPolicy::PrimaryOnly)
|
||||
//! .height(64)
|
||||
//! .build()?
|
||||
//! .run()?;
|
||||
//! # Ok::<(), layer_shika::Error>(())
|
||||
//! ```
|
||||
|
||||
|
|
@ -75,9 +98,10 @@ pub mod window;
|
|||
pub use layer_shika_composition::{Error, Result};
|
||||
|
||||
pub use shell::{
|
||||
DEFAULT_WINDOW_NAME, LayerShika, LayerSurfaceHandle, Shell, ShellComposition, ShellControl,
|
||||
ShellEventContext, ShellRuntime, ShellWindowConfigHandler, ShellWindowDefinition,
|
||||
ShellWindowHandle, SingleWindowShell,
|
||||
DEFAULT_COMPONENT_NAME, DEFAULT_WINDOW_NAME, LayerShika, LayerShikaEventContext,
|
||||
LayerShikaEventLoopHandle, LayerSurfaceHandle, Runtime, Shell, ShellBuilder, ShellControl,
|
||||
ShellEventContext, ShellRuntime, ShellWindowConfigHandler, ShellWindowHandle,
|
||||
SingleWindowShell, WindowConfigBuilder, WindowDefinition,
|
||||
};
|
||||
|
||||
pub use window::{
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@
|
|||
#![allow(clippy::pub_use)]
|
||||
|
||||
pub use crate::shell::{
|
||||
DEFAULT_WINDOW_NAME, LayerShika, LayerSurfaceHandle, Shell, ShellComposition, ShellControl,
|
||||
DEFAULT_COMPONENT_NAME, DEFAULT_WINDOW_NAME, LayerShika, LayerShikaEventContext,
|
||||
LayerShikaEventLoopHandle, LayerSurfaceHandle, Runtime, Shell, ShellBuilder, ShellControl,
|
||||
ShellEventContext, ShellEventLoopHandle, ShellRuntime, ShellWindowConfigHandler,
|
||||
ShellWindowDefinition, ShellWindowHandle, SingleWindowShell,
|
||||
ShellWindowHandle, SingleWindowShell, WindowConfigBuilder, WindowDefinition,
|
||||
};
|
||||
|
||||
pub use crate::window::{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pub use layer_shika_composition::{
|
||||
DEFAULT_WINDOW_NAME, LayerShika, LayerSurfaceHandle, Shell, ShellComposition, ShellControl,
|
||||
DEFAULT_COMPONENT_NAME, DEFAULT_WINDOW_NAME, LayerShika, LayerShikaEventContext,
|
||||
LayerShikaEventLoopHandle, LayerSurfaceHandle, Runtime, Shell, ShellBuilder, ShellControl,
|
||||
ShellEventContext, ShellEventLoopHandle, ShellRuntime, ShellWindowConfigHandler,
|
||||
ShellWindowDefinition, ShellWindowHandle, SingleWindowShell,
|
||||
ShellWindowHandle, SingleWindowShell, WindowConfigBuilder, WindowDefinition,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue