use crate::core::{Rect, Size};
use crate::ontology::*;
pub type WindowId = u64;
#[derive(Debug, Clone)]
pub struct WindowConfig {
pub title: String,
pub size: Size,
pub resizable: bool,
pub always_on_top: bool,
pub decorated: bool,
pub transparent: bool,
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
title: "Dewey Window".to_string(),
size: Size::new(800.0, 600.0),
resizable: true,
always_on_top: false,
decorated: true,
transparent: false,
}
}
}
impl WindowConfig {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
..Default::default()
}
}
pub fn with_size(mut self, width: f32, height: f32) -> Self {
self.size = Size::new(width, height);
self
}
pub fn with_resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
pub fn with_always_on_top(mut self, on_top: bool) -> Self {
self.always_on_top = on_top;
self
}
pub fn with_decorated(mut self, decorated: bool) -> Self {
self.decorated = decorated;
self
}
}
#[derive(Debug)]
pub struct WindowState {
pub id: WindowId,
pub config: WindowConfig,
pub bounds: Rect,
pub visible: bool,
pub focused: bool,
}
pub struct WindowManager {
windows: Vec<WindowState>,
next_id: WindowId,
}
impl WindowManager {
pub fn new() -> Self {
Self {
windows: Vec::new(),
next_id: 1,
}
}
pub fn open(&mut self, config: WindowConfig) -> WindowId {
let id = self.next_id;
self.next_id += 1;
let bounds = Rect::new(0.0, 0.0, config.size.width, config.size.height);
self.windows.push(WindowState {
id,
config,
bounds,
visible: true,
focused: true,
});
id
}
pub fn close(&mut self, id: WindowId) {
self.windows.retain(|w| w.id != id);
}
pub fn get(&self, id: WindowId) -> Option<&WindowState> {
self.windows.iter().find(|w| w.id == id)
}
pub fn get_mut(&mut self, id: WindowId) -> Option<&mut WindowState> {
self.windows.iter_mut().find(|w| w.id == id)
}
pub fn windows(&self) -> &[WindowState] {
&self.windows
}
pub fn count(&self) -> usize {
self.windows.len()
}
pub fn focus(&mut self, id: WindowId) {
for w in &mut self.windows {
w.focused = w.id == id;
}
}
pub fn focused(&self) -> Option<WindowId> {
self.windows.iter().find(|w| w.focused).map(|w| w.id)
}
pub fn set_visible(&mut self, id: WindowId, visible: bool) {
if let Some(w) = self.get_mut(id) {
w.visible = visible;
}
}
}
impl Default for WindowManager {
fn default() -> Self {
Self::new()
}
}
impl Discoverable for WindowManager {
fn schema(&self) -> WidgetSchema {
let mut schema = WidgetSchema::new(
"WindowManager",
"Manages multiple application windows with focus tracking",
SemanticRole::System,
);
schema.usage_hint = Some("WindowManager::new().open(WindowConfig::new(\"Title\"))".into());
schema.tags = vec!["window".into(), "multi-window".into(), "manager".into()];
schema
}
fn capabilities(&self) -> Vec<AgentCapability> {
vec![
AgentCapability::Focusable,
AgentCapability::Closable,
AgentCapability::Minimizable,
AgentCapability::Maximizable,
]
}
fn actions(&self) -> Vec<AgentAction> {
vec![
AgentAction::with_params(
"open",
"Open a new window",
vec![ActionParam::required(
"title",
"Window title",
ActionParamType::String,
)],
true,
),
AgentAction::with_params(
"close",
"Close a window by ID",
vec![ActionParam::required(
"id",
"Window ID",
ActionParamType::Index,
)],
true,
),
AgentAction::with_params(
"focus",
"Set focus to a window",
vec![ActionParam::required(
"id",
"Window ID",
ActionParamType::Index,
)],
true,
),
AgentAction::with_params(
"set_visible",
"Show or hide a window",
vec![
ActionParam::required("id", "Window ID", ActionParamType::Index),
ActionParam::required("visible", "Visibility", ActionParamType::Boolean),
],
true,
),
AgentAction::simple("list", "List all windows", false),
]
}
fn semantic_role(&self) -> SemanticRole {
SemanticRole::System
}
fn agent_state(&self) -> serde_json::Value {
let windows: Vec<serde_json::Value> = self
.windows
.iter()
.map(|w| {
serde_json::json!({
"id": w.id,
"title": w.config.title,
"width": w.bounds.width,
"height": w.bounds.height,
"visible": w.visible,
"focused": w.focused,
"resizable": w.config.resizable,
"decorated": w.config.decorated,
})
})
.collect();
serde_json::json!({
"window_count": self.windows.len(),
"windows": windows,
"focused_id": self.focused(),
})
}
fn execute_action(
&mut self,
action: &str,
params: &serde_json::Value,
) -> Result<serde_json::Value, String> {
match action {
"open" => {
let title = params["title"].as_str().ok_or("missing title")?.to_string();
let id = self.open(WindowConfig::new(title));
Ok(serde_json::json!({ "id": id }))
}
"close" => {
let id = params["id"].as_u64().ok_or("missing id")?;
self.close(id);
Ok(serde_json::json!({ "closed": id }))
}
"focus" => {
let id = params["id"].as_u64().ok_or("missing id")?;
self.focus(id);
Ok(serde_json::json!({ "focused": id }))
}
"set_visible" => {
let id = params["id"].as_u64().ok_or("missing id")?;
let visible = params["visible"].as_bool().ok_or("missing visible")?;
self.set_visible(id, visible);
Ok(serde_json::json!({ "id": id, "visible": visible }))
}
"list" => Ok(self.agent_state()),
_ => Err(format!("Unknown action: {action}")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_and_close() {
let mut wm = WindowManager::new();
let id1 = wm.open(WindowConfig::new("Window 1"));
let id2 = wm.open(WindowConfig::new("Window 2"));
assert_eq!(wm.count(), 2);
assert_eq!(wm.get(id1).unwrap().config.title, "Window 1");
wm.close(id1);
assert_eq!(wm.count(), 1);
assert!(wm.get(id1).is_none());
assert!(wm.get(id2).is_some());
}
#[test]
fn focus_management() {
let mut wm = WindowManager::new();
let id1 = wm.open(WindowConfig::new("A"));
let id2 = wm.open(WindowConfig::new("B"));
assert!(wm.get(id2).unwrap().focused);
wm.focus(id1);
assert!(wm.get(id1).unwrap().focused);
assert!(!wm.get(id2).unwrap().focused);
assert_eq!(wm.focused(), Some(id1));
}
}