diff --git a/apps/magpie/src/client.rs b/apps/magpie/src/client.rs index 652c7f2..5714552 100644 --- a/apps/magpie/src/client.rs +++ b/apps/magpie/src/client.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use serde::Serialize; use std::os::unix::net::UnixStream; diff --git a/apps/magpie/src/lib.rs b/apps/magpie/src/lib.rs index 233623e..c126da0 100644 --- a/apps/magpie/src/lib.rs +++ b/apps/magpie/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + pub mod client; pub mod protocol; diff --git a/apps/magpie/src/main.rs b/apps/magpie/src/main.rs index d53eb7a..372dd9c 100644 --- a/apps/magpie/src/main.rs +++ b/apps/magpie/src/main.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use glium::glutin::event_loop::EventLoopBuilder; use canary_magpie::service::*; diff --git a/apps/magpie/src/protocol.rs b/apps/magpie/src/protocol.rs index a833bc3..6d83b6f 100644 --- a/apps/magpie/src/protocol.rs +++ b/apps/magpie/src/protocol.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use std::collections::VecDeque; use std::io::{Read, Write}; use std::marker::PhantomData; @@ -21,6 +24,7 @@ pub type PanelId = u32; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreatePanel { pub id: PanelId, + pub protocol: String, pub script: PathBuf, } diff --git a/apps/magpie/src/service/gl.rs b/apps/magpie/src/service/gl.rs index 9c0a620..689b340 100644 --- a/apps/magpie/src/service/gl.rs +++ b/apps/magpie/src/service/gl.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use canary::{DrawCommand, Vec2, PX_PER_MM}; use glium::Surface; diff --git a/apps/magpie/src/service/ipc.rs b/apps/magpie/src/service/ipc.rs index 735ee07..869e297 100644 --- a/apps/magpie/src/service/ipc.rs +++ b/apps/magpie/src/service/ipc.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; @@ -96,7 +99,11 @@ impl Client { while let Some(msg) = self.messenger.recv() { println!("Client #{}: {:?}", self.token.0, msg); match msg { - MagpieServerMsg::CreatePanel(CreatePanel { id, script }) => { + MagpieServerMsg::CreatePanel(CreatePanel { + id, + protocol, + script, + }) => { let window = self.data.write().new_window_id(); if let Some(old_id) = self.id_to_window.insert(id, window) { @@ -104,7 +111,11 @@ impl Client { let _ = self.window_sender.send_event(msg); } - let msg = WindowMessage::OpenWindow { id: window, script }; + let msg = WindowMessage::OpenWindow { + id: window, + protocol, + script, + }; let _ = self.window_sender.send_event(msg); } MagpieServerMsg::SendMessage(SendMessage { id, msg }) => { diff --git a/apps/magpie/src/service/mod.rs b/apps/magpie/src/service/mod.rs index b57a6fe..15d5746 100644 --- a/apps/magpie/src/service/mod.rs +++ b/apps/magpie/src/service/mod.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + pub mod gl; pub mod ipc; pub mod window; diff --git a/apps/magpie/src/service/window.rs b/apps/magpie/src/service/window.rs index 624b7aa..3834224 100644 --- a/apps/magpie/src/service/window.rs +++ b/apps/magpie/src/service/window.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use std::collections::HashMap; use std::path::PathBuf; use std::time::Instant; @@ -14,10 +17,19 @@ use crate::service::ipc::{IpcMessage, IpcMessageSender}; #[derive(Clone, Debug)] pub enum WindowMessage { - OpenWindow { id: usize, script: PathBuf }, - CloseWindow { id: usize }, + OpenWindow { + id: usize, + protocol: String, + script: PathBuf, + }, + CloseWindow { + id: usize, + }, Quit, - SendMessage { id: usize, msg: Vec }, + SendMessage { + id: usize, + msg: Vec, + }, } pub type WindowMessageSender = EventLoopProxy; @@ -152,11 +164,11 @@ impl WindowStore { message: WindowMessage, ) -> anyhow::Result { match message { - WindowMessage::OpenWindow { id, script } => { + WindowMessage::OpenWindow { id, protocol, script } => { println!("Opening window {} with script {:?}", id, script); let module = std::fs::read(script)?; let mut script = self.runtime.load_module(&module)?; - let panel = script.create_panel(vec![])?; + let panel = script.create_panel(&protocol, vec![])?; let window = Window::new(panel, &event_loop)?; let window_id = window.get_id(); self.windows.insert(window_id, window); diff --git a/apps/music-player/Cargo.toml b/apps/music-player/Cargo.toml index bc71b4a..2aed4fe 100644 --- a/apps/music-player/Cargo.toml +++ b/apps/music-player/Cargo.toml @@ -2,6 +2,7 @@ name = "canary-music-player" version = "0.1.0" edition = "2021" +license = "AGPL-3.0-or-later" [[bin]] name = "canary-music-player" diff --git a/apps/music-player/src/lib.rs b/apps/music-player/src/lib.rs index 2c04850..d3ecabb 100644 --- a/apps/music-player/src/lib.rs +++ b/apps/music-player/src/lib.rs @@ -1,9 +1,12 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use serde::{Deserialize, Serialize}; pub use serde; pub use serde_json; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub enum PlaybackStatus { /// A track is currently playing. Playing, @@ -15,7 +18,7 @@ pub enum PlaybackStatus { Stopped, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub enum LoopStatus { /// The playback will stop when there are no more tracks to play. None, diff --git a/apps/music-player/src/main.rs b/apps/music-player/src/main.rs index d142d49..5f07f12 100644 --- a/apps/music-player/src/main.rs +++ b/apps/music-player/src/main.rs @@ -1,6 +1,10 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use canary_music_player::*; use canary_magpie::client::MagpieClient; use canary_magpie::protocol::{CreatePanel, MagpieServerMsg}; +use canary_music_player::*; use mpris::PlayerFinder; pub struct MetadataTracker { @@ -8,8 +12,8 @@ pub struct MetadataTracker { pub track: TrackInfo, } -impl From for MetadataTracker { - fn from(metadata: mpris::Metadata) -> Self { +impl From<&mpris::Metadata> for MetadataTracker { + fn from(metadata: &mpris::Metadata) -> Self { let album = AlbumInfo { title: metadata.album_name().map(ToString::to_string), artists: metadata @@ -36,14 +40,21 @@ impl From for MetadataTracker { } impl MetadataTracker { - pub fn new(magpie: &mut MagpieClient, metadata: mpris::Metadata) -> Self { + pub fn new(magpie: &mut MagpieClient, metadata: &mpris::Metadata) -> Self { let new: Self = metadata.into(); magpie.send_json_message(0, &InMsg::AlbumChanged(new.album.clone())); magpie.send_json_message(0, &InMsg::TrackChanged(new.track.clone())); + magpie.send_json_message( + 0, + &InMsg::ProgressChanged(ProgressChanged { + position: 0.0, + length: metadata.length().map(|l| l.as_secs_f32()), + }), + ); new } - pub fn update(&mut self, messenger: &mut MagpieClient, metadata: mpris::Metadata) { + pub fn update(&mut self, messenger: &mut MagpieClient, metadata: &mpris::Metadata) { let new: Self = metadata.into(); if self.album != new.album { @@ -52,6 +63,13 @@ impl MetadataTracker { if self.track != new.track { messenger.send_json_message(0, &InMsg::TrackChanged(new.track.clone())); + messenger.send_json_message( + 0, + &InMsg::ProgressChanged(ProgressChanged { + position: 0.0, + length: metadata.length().map(|l| l.as_secs_f32()), + }), + ); } *self = new; @@ -68,8 +86,9 @@ fn main() { let player_finder = PlayerFinder::new().expect("Could not connect to D-Bus"); let mut magpie = MagpieClient::new().unwrap(); + let protocol = "tebibyte-media.desktop.music-player-controller".to_string(); let script = std::path::PathBuf::from(&module_path); - let msg = CreatePanel { id: 0, script }; + let msg = CreatePanel { id: 0, protocol, script }; let msg = MagpieServerMsg::CreatePanel(msg); magpie.messenger.send(&msg).unwrap(); @@ -110,7 +129,7 @@ fn main() { magpie.send_json_message(0, &InMsg::Connected); let metadata = player.get_metadata().unwrap(); - let mut metadata_tracker = MetadataTracker::new(&mut magpie, metadata); + let mut metadata_tracker = MetadataTracker::new(&mut magpie, &metadata); let mut events = match player.events() { Ok(events) => events, @@ -150,7 +169,7 @@ fn main() { volume: volume as f32, }), PlayerShutDown => None, - TrackChanged(metadata) => { + TrackChanged(ref metadata) => { metadata_tracker.update(&mut magpie, metadata); None } diff --git a/apps/sandbox/src/main.rs b/apps/sandbox/src/main.rs index a6381eb..9ec1f57 100644 --- a/apps/sandbox/src/main.rs +++ b/apps/sandbox/src/main.rs @@ -1,7 +1,7 @@ // Copyright (c) 2022 Marceline Cramer // SPDX-License-Identifier: AGPL-3.0-or-later -use canary::{CursorEventKind, Panel, Runtime, Script}; +use canary::{CursorEventKind, Panel, Runtime, Script, PX_PER_MM}; use eframe::egui; use std::time::Instant; @@ -31,6 +31,7 @@ struct App { panels: Vec, next_idx: usize, last_update: Instant, + protocol_buf: String, bind_message_buf: String, } @@ -46,6 +47,7 @@ impl App { panels: vec![], next_idx: 0, last_update: Instant::now(), + protocol_buf: String::new(), bind_message_buf: String::new(), } } @@ -56,12 +58,16 @@ impl eframe::App for App { ctx.request_repaint(); egui::SidePanel::left("left_panel").show(ctx, |ui| { + ui.label("Protocol name:"); + ui.text_edit_singleline(&mut self.protocol_buf); + + ui.label("Bind message:"); let text_edit = egui::TextEdit::multiline(&mut self.bind_message_buf).code_editor(); ui.add(text_edit); if ui.button("Bind Panel").clicked() { let msg = self.bind_message_buf.as_bytes().to_vec(); - let panel = self.script.create_panel(msg).unwrap(); + let panel = self.script.create_panel(&self.protocol_buf, msg).unwrap(); let index = self.next_idx; self.next_idx += 1; @@ -70,6 +76,7 @@ impl eframe::App for App { index, msg_buf: String::new(), show_msg: false, + current_size: Default::default(), }; self.panels.push(panel); @@ -91,6 +98,7 @@ pub struct PanelWindow { pub index: usize, pub msg_buf: String, pub show_msg: bool, + pub current_size: egui::Vec2, } impl PanelWindow { @@ -101,21 +109,27 @@ impl PanelWindow { ui.checkbox(&mut self.show_msg, "Show Message Editor"); }); - let size = egui::vec2(800.0, 800.0); let sense = egui::Sense { click: true, drag: true, focusable: true, }; - let (rect, response) = ui.allocate_at_least(size, sense); + let desired_size = ui.available_size(); + let response = ui.allocate_response(desired_size, sense); + let rect = response.rect; + + if rect.size() != self.current_size { + let size = rect.size(); + self.current_size = size; + + let size = canary::Vec2::new(size.x, size.y); + self.panel.on_resize(size * PX_PER_MM); + } if let Some(hover_pos) = response.hover_pos() { - let local = (hover_pos - rect.left_top()) / rect.size(); - let norm = local * 2.0 - egui::vec2(1.0, 1.0); - let x = norm.x; - let y = -norm.y; - let pos = canary::Vec2 { x, y }; + let local = (hover_pos - rect.left_top()) * PX_PER_MM; + let pos = canary::Vec2::new(local.x, local.y); let kind = if response.drag_started() { CursorEventKind::Select @@ -142,9 +156,9 @@ impl PanelWindow { canary::DrawCommand::Mesh { vertices, indices } => { for v in vertices.iter() { use egui::epaint::Vertex; - let pos = egui::pos2(v.position.x, -v.position.y); - let pos = pos.to_vec2() / 2.0 + egui::vec2(0.5, 0.5); - let pos = rect.left_top() + pos * rect.size(); + let pos = v.position / PX_PER_MM; + let pos = egui::pos2(pos.x, pos.y); + let pos = pos + rect.left_top().to_vec2(); let (r, g, b, a) = v.color.to_rgba_unmultiplied(); let color = egui::Color32::from_rgba_unmultiplied(r, g, b, a); let v = Vertex { pos, uv, color }; diff --git a/crates/script/src/api/abi.rs b/crates/script/src/api/abi.rs index 0e70bc5..4ebc255 100644 --- a/crates/script/src/api/abi.rs +++ b/crates/script/src/api/abi.rs @@ -7,11 +7,17 @@ use super::*; static mut PANEL_IMPLS: Vec> = Vec::new(); -pub fn bind_panel(panel: u32, msg: u32) -> u32 { +pub fn bind_panel( + cb: impl Fn(Panel, Message, Message) -> Box, + panel: u32, + protocol: u32, + msg: u32, +) -> u32 { unsafe { let panel = Panel(panel); + let protocol = Message(protocol); let msg = Message(msg); - let panel_impl = T::bind(panel, msg); + let panel_impl = cb(panel, protocol, msg); let id = PANEL_IMPLS.len() as u32; PANEL_IMPLS.push(panel_impl); id diff --git a/crates/script/src/api/mod.rs b/crates/script/src/api/mod.rs index b208388..56484e1 100644 --- a/crates/script/src/api/mod.rs +++ b/crates/script/src/api/mod.rs @@ -9,10 +9,10 @@ pub mod abi; #[macro_export] macro_rules! export_abi { - ($panel_impl: ident) => { + ($bind_panel: ident) => { #[no_mangle] - pub extern "C" fn bind_panel(panel: u32, msg: u32) -> u32 { - ::canary_script::api::abi::bind_panel::<$panel_impl>(panel, msg) + pub extern "C" fn bind_panel(panel: u32, protocol: u32, msg: u32) -> u32 { + ::canary_script::api::abi::bind_panel($bind_panel, panel, protocol, msg) } #[no_mangle] @@ -42,10 +42,6 @@ macro_rules! export_abi { }; } -pub trait BindPanel { - fn bind(panel: Panel, msg: Message) -> Box; -} - pub trait PanelImpl { fn update(&mut self, dt: f32); fn draw(&mut self); @@ -375,7 +371,6 @@ impl DrawContext { self.draw_rect(bottom_edge, color); } - self.draw_rect(inner_rect, color); } diff --git a/scripts/music-player/Cargo.toml b/scripts/music-player/Cargo.toml index 8a0e3b8..b7191ae 100644 --- a/scripts/music-player/Cargo.toml +++ b/scripts/music-player/Cargo.toml @@ -2,6 +2,7 @@ name = "canary-music-player-script" version = "0.1.0" edition = "2021" +license = "AGPL-3.0-or-later" [lib] crate-type = ["cdylib"] diff --git a/scripts/music-player/src/lib.rs b/scripts/music-player/src/lib.rs index 5d310a8..956df2b 100644 --- a/scripts/music-player/src/lib.rs +++ b/scripts/music-player/src/lib.rs @@ -1,10 +1,17 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -use canary_script::*; use api::*; +use canary_script::*; -canary_script::export_abi!(MusicPlayerPanel); +canary_script::export_abi!(bind_panel_impl); + +pub fn bind_panel_impl(panel: Panel, _protocol: Message, message: Message) -> Box { + MusicPlayerPanel::bind(panel, message) +} const DISPLAY_FONT: &str = "Liberation Sans"; @@ -14,19 +21,6 @@ pub struct MusicPlayerPanel { label: Label, } -impl BindPanel for MusicPlayerPanel { - fn bind(panel: Panel, message: Message) -> Box { - let display_font = Font::new(DISPLAY_FONT); - let label = Label::new(display_font, "Hello, world!".into(), 1.2); - let panel = Self { - panel, - display_font, - label, - }; - Box::new(panel) - } -} - impl PanelImpl for MusicPlayerPanel { fn update(&mut self, dt: f32) {} @@ -39,20 +33,31 @@ impl PanelImpl for MusicPlayerPanel { self.label.draw(&ctx, offset, size, color); } - fn on_resize(&mut self, new_size: Vec2) { - - } + fn on_resize(&mut self, new_size: Vec2) {} fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {} fn on_message(&mut self, msg: Message) { - use canary_music_player::{InMsg, serde_json}; + use canary_music_player::{serde_json, InMsg}; let msg = msg.to_vec(); let msg = serde_json::from_slice::(&msg); self.label.set_text(format!("{:#?}", msg)); } } +impl MusicPlayerPanel { + pub fn bind(panel: Panel, _message: Message) -> Box { + let display_font = Font::new(DISPLAY_FONT); + let label = Label::new(display_font, "Hello, world!".into(), 1.2); + let panel = Self { + panel, + display_font, + label, + }; + Box::new(panel) + } +} + pub struct Label { font: Font, text: String, diff --git a/scripts/sao-ui/Cargo.toml b/scripts/sao-ui/Cargo.toml index 6c6862e..2a09cd0 100644 --- a/scripts/sao-ui/Cargo.toml +++ b/scripts/sao-ui/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib"] [dependencies] glam = "^0.21" keyframe = "1" +canary-music-player = { path = "../../apps/music-player" } canary-script = { path = "../../crates/script" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/scripts/sao-ui/src/anim.rs b/scripts/sao-ui/src/anim.rs index bc07f1c..10aae47 100644 --- a/scripts/sao-ui/src/anim.rs +++ b/scripts/sao-ui/src/anim.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use crate::Color; use keyframe::EasingFunction; diff --git a/scripts/sao-ui/src/draw.rs b/scripts/sao-ui/src/draw.rs deleted file mode 100644 index 8b13789..0000000 --- a/scripts/sao-ui/src/draw.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/scripts/sao-ui/src/lib.rs b/scripts/sao-ui/src/lib.rs index b24baef..b558d3f 100644 --- a/scripts/sao-ui/src/lib.rs +++ b/scripts/sao-ui/src/lib.rs @@ -1,18 +1,31 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub mod anim; -pub mod draw; pub mod main_menu; -pub mod panel; +pub mod music_player; pub mod widgets; -use canary_script::*; use api::*; -use widgets::Widget; +use canary_script::*; use main_menu::MainMenuPanel; +use music_player::MusicPlayerPanel; +use widgets::Widget; -export_abi!(MainMenuPanel); +export_abi!(bind_panel_impl); + +fn bind_panel_impl(panel: Panel, protocol: Message, msg: Message) -> Box { + let protocol = protocol.to_vec(); + let protocol = String::from_utf8(protocol).unwrap(); + + match protocol.as_str() { + "tebibyte-media.desktop.music-player-controller" => MusicPlayerPanel::bind(panel, msg), + _ => MainMenuPanel::bind(panel, msg), + } +} pub const ICON_FONT: &str = "Iosevka Nerd Font"; pub const DISPLAY_FONT: &str = "Homenaje"; @@ -23,18 +36,6 @@ pub struct ConfirmationDialogPanel { dialog: widgets::dialog::Dialog, } -impl BindPanel for ConfirmationDialogPanel { - fn bind(panel: Panel, msg: Message) -> Box { - let msg = msg.to_vec(); - let info: DialogInfo = serde_json::from_slice(&msg).unwrap(); - - use widgets::dialog::*; - let style = DialogStyle::default(); - let dialog = Dialog::new(style, &info); - Box::new(Self { panel, dialog }) - } -} - impl PanelImpl for ConfirmationDialogPanel { fn update(&mut self, dt: f32) { self.dialog.update(dt); @@ -53,3 +54,15 @@ impl PanelImpl for ConfirmationDialogPanel { fn on_message(&mut self, _msg: Message) {} } + +impl ConfirmationDialogPanel { + pub fn bind(panel: Panel, msg: Message) -> Box { + let msg = msg.to_vec(); + let info: DialogInfo = serde_json::from_slice(&msg).unwrap(); + + use widgets::dialog::*; + let style = DialogStyle::default(); + let dialog = Dialog::new(style, &info); + Box::new(Self { panel, dialog }) + } +} diff --git a/scripts/sao-ui/src/main_menu.rs b/scripts/sao-ui/src/main_menu.rs index d30e0bb..87bbbb0 100644 --- a/scripts/sao-ui/src/main_menu.rs +++ b/scripts/sao-ui/src/main_menu.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + use crate::widgets::prelude::*; use crate::{DrawContext, Rect}; @@ -12,15 +15,6 @@ pub struct MainMenuPanel { menu: MainMenu, } -impl BindPanel for MainMenuPanel { - fn bind(panel: Panel, msg: Message) -> Box { - Box::new(Self { - panel, - menu: MainMenu::default(), - }) - } -} - impl PanelImpl for MainMenuPanel { fn update(&mut self, dt: f32) { Widget::update(&mut self.menu, dt); @@ -40,6 +34,15 @@ impl PanelImpl for MainMenuPanel { fn on_message(&mut self, msg: Message) {} } +impl MainMenuPanel { + pub fn bind(panel: Panel, msg: Message) -> Box { + Box::new(Self { + panel, + menu: MainMenu::default(), + }) + } +} + pub struct MainMenu { pub menu: Offset>, pub player_info: Reveal>, diff --git a/scripts/sao-ui/src/music_player.rs b/scripts/sao-ui/src/music_player.rs new file mode 100644 index 0000000..9eab50e --- /dev/null +++ b/scripts/sao-ui/src/music_player.rs @@ -0,0 +1,362 @@ +use api::*; +use canary_script::*; + +use canary_music_player::{AlbumInfo, PlaybackStatus, ProgressChanged, TrackInfo}; + +use crate::widgets::prelude::*; +use button::{RoundButton, RoundButtonStyle}; +use dialog::{DialogBodyStyle, DialogFooterStyle}; +use shell::Offset; +use text::{HorizontalAlignment, Label, LabelText}; + +pub struct MusicPlayerPanel { + panel: Panel, + widget: Option, + disconnected: Offset