Merge branch 'main' into sao-music-player
This commit is contained in:
commit
317541486c
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
pub mod client;
|
||||
pub mod protocol;
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<u8> },
|
||||
SendMessage {
|
||||
id: usize,
|
||||
msg: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
pub type WindowMessageSender = EventLoopProxy<WindowMessage>;
|
||||
|
@ -152,11 +164,11 @@ impl WindowStore {
|
|||
message: WindowMessage,
|
||||
) -> anyhow::Result<bool> {
|
||||
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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<mpris::Metadata> 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<mpris::Metadata> 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
|
||||
}
|
||||
|
|
|
@ -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<PanelWindow>,
|
||||
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 };
|
||||
|
|
|
@ -7,11 +7,17 @@ use super::*;
|
|||
|
||||
static mut PANEL_IMPLS: Vec<Box<dyn PanelImpl>> = Vec::new();
|
||||
|
||||
pub fn bind_panel<T: BindPanel>(panel: u32, msg: u32) -> u32 {
|
||||
pub fn bind_panel(
|
||||
cb: impl Fn(Panel, Message, Message) -> Box<dyn PanelImpl>,
|
||||
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
|
||||
|
|
|
@ -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<dyn PanelImpl>;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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<dyn PanelImpl> {
|
||||
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<dyn PanelImpl> {
|
||||
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::<InMsg>(&msg);
|
||||
self.label.set_text(format!("{:#?}", msg));
|
||||
}
|
||||
}
|
||||
|
||||
impl MusicPlayerPanel {
|
||||
pub fn bind(panel: Panel, _message: Message) -> Box<dyn PanelImpl> {
|
||||
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,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::Color;
|
||||
use keyframe::EasingFunction;
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
// 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 music_player;
|
||||
pub mod panel;
|
||||
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!(MusicPlayerPanel);
|
||||
export_abi!(bind_panel_impl);
|
||||
|
||||
fn bind_panel_impl(panel: Panel, _protocol: Message, msg: Message) -> Box<dyn PanelImpl> {
|
||||
MainMenuPanel::bind(panel, msg)
|
||||
}
|
||||
|
||||
pub const ICON_FONT: &str = "Iosevka Nerd Font";
|
||||
pub const DISPLAY_FONT: &str = "Homenaje";
|
||||
|
@ -25,18 +29,6 @@ pub struct ConfirmationDialogPanel {
|
|||
dialog: widgets::dialog::Dialog,
|
||||
}
|
||||
|
||||
impl BindPanel for ConfirmationDialogPanel {
|
||||
fn bind(panel: Panel, msg: Message) -> Box<dyn PanelImpl> {
|
||||
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);
|
||||
|
@ -55,3 +47,15 @@ impl PanelImpl for ConfirmationDialogPanel {
|
|||
|
||||
fn on_message(&mut self, _msg: Message) {}
|
||||
}
|
||||
|
||||
impl ConfirmationDialogPanel {
|
||||
pub fn bind(panel: Panel, msg: Message) -> Box<dyn PanelImpl> {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<dyn PanelImpl> {
|
||||
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<dyn PanelImpl> {
|
||||
Box::new(Self {
|
||||
panel,
|
||||
menu: MainMenu::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MainMenu {
|
||||
pub menu: Offset<SlotMenu<RoundButton>>,
|
||||
pub player_info: Reveal<Offset<PlayerInfo>>,
|
||||
|
|
|
@ -15,24 +15,6 @@ pub struct MusicPlayerPanel {
|
|||
disconnected: Offset<Label>,
|
||||
}
|
||||
|
||||
impl BindPanel for MusicPlayerPanel {
|
||||
fn bind(panel: Panel, _msg: Message) -> Box<dyn PanelImpl> {
|
||||
let dc_text = LabelText {
|
||||
font: Font::new(crate::DISPLAY_FONT),
|
||||
text: "Disconnected".to_string(),
|
||||
};
|
||||
|
||||
let disconnected = Label::new_centered(dc_text, 10.0, Color::WHITE);
|
||||
let disconnected = Offset::new(disconnected, Vec2::ZERO);
|
||||
|
||||
Box::new(Self {
|
||||
panel,
|
||||
widget: None,
|
||||
disconnected,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelImpl for MusicPlayerPanel {
|
||||
fn update(&mut self, dt: f32) {
|
||||
if let Some(widget) = self.widget.as_mut() {
|
||||
|
@ -85,6 +67,24 @@ impl PanelImpl for MusicPlayerPanel {
|
|||
}
|
||||
}
|
||||
|
||||
impl MusicPlayerPanel {
|
||||
pub fn bind(panel: Panel, _msg: Message) -> Box<dyn PanelImpl> {
|
||||
let dc_text = LabelText {
|
||||
font: Font::new(crate::DISPLAY_FONT),
|
||||
text: "Disconnected".to_string(),
|
||||
};
|
||||
|
||||
let disconnected = Label::new_centered(dc_text, 10.0, Color::WHITE);
|
||||
let disconnected = Offset::new(disconnected, Vec2::ZERO);
|
||||
|
||||
Box::new(Self {
|
||||
panel,
|
||||
widget: None,
|
||||
disconnected,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MusicPlayerStyle {
|
||||
pub body: DialogBodyStyle,
|
||||
pub footer: DialogFooterStyle,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SDPX-License-Identifier: LGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
//! This module defines backends for WebAssembly execution.
|
||||
//!
|
||||
|
@ -22,7 +22,7 @@ pub fn make_default_backend() -> anyhow::Result<Box<dyn Backend>> {
|
|||
|
||||
/// A WebAssembly runtime backend.
|
||||
pub trait Backend {
|
||||
fn load_module(&self, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>>;
|
||||
fn load_module(&self, abi: ScriptAbi, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>>;
|
||||
}
|
||||
|
||||
/// An instance of a WebAssembly module.
|
||||
|
@ -36,10 +36,11 @@ pub trait Instance {
|
|||
/// Binds script data to a Canary panel.
|
||||
///
|
||||
/// To "bind" a Canary panel to a Canary script, this function must be
|
||||
/// called. It passes the ID of a panel to the script, plus an
|
||||
/// initialization message, and the script returns an integer as
|
||||
/// userdata. All panel events will be identified to the script with this
|
||||
/// userdata as the first argument.
|
||||
/// called. It passes the ID of a panel to the script, the name of the
|
||||
/// protocol that this panel will be using, plus an initialization
|
||||
/// message, and the script returns an integer as userdata. All panel
|
||||
/// events will be identified to the script with this userdata as the first
|
||||
/// argument.
|
||||
///
|
||||
/// The intended usecase for this userdata is to contain a pointer. A
|
||||
/// Canary script can allocate some high-level object in memory, and when
|
||||
|
@ -47,7 +48,7 @@ pub trait Instance {
|
|||
/// userdata. Then, when the runtime calls back into the script, the
|
||||
/// userdata will be reinterpreted as a pointer and a method can be called
|
||||
/// on that object in memory.
|
||||
fn bind_panel(&self, panel: PanelId, msg: Vec<u8>) -> u32;
|
||||
fn bind_panel(&self, panel: PanelId, protocol: &str, msg: Vec<u8>) -> u32;
|
||||
|
||||
fn update(&self, panel_ud: u32, dt: f32);
|
||||
|
||||
|
@ -59,3 +60,101 @@ pub trait Instance {
|
|||
|
||||
fn on_message(&self, panel_ud: u32, msg: Vec<u8>);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ScriptAbi {
|
||||
draw_cmds: Mutex<Vec<DrawCommand>>,
|
||||
font_store: Arc<text::FontStore>,
|
||||
font_families: Mutex<HashMap<String, u32>>,
|
||||
loaded_fonts: RwLock<Vec<Arc<text::Font>>>,
|
||||
text_layouts: RwLock<Slab<text::TextLayout>>,
|
||||
message_store: RwLock<Slab<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl ScriptAbi {
|
||||
pub fn new(font_store: Arc<text::FontStore>) -> Self {
|
||||
Self {
|
||||
font_store,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_draw(&self) {
|
||||
let mut lock = self.draw_cmds.lock();
|
||||
lock.clear();
|
||||
}
|
||||
|
||||
pub fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) {
|
||||
self.draw_cmds.lock().push(DrawCommand::Mesh {
|
||||
vertices: vertices.to_vec(),
|
||||
indices: indices.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw_text_layout(&self, id: u32, offset: Vec2, scale: f32, color: Color) {
|
||||
// TODO multiple fonts per layout
|
||||
let layouts = self.text_layouts.read();
|
||||
let layout = layouts.get(id as usize).unwrap();
|
||||
let glyphs = layout.glyphs.as_slice();
|
||||
let loaded = self.loaded_fonts.read();
|
||||
let font = loaded.get(layout.font_id as usize).unwrap();
|
||||
let cmds = font.draw(glyphs, offset, scale, color);
|
||||
self.draw_cmds.lock().extend(cmds.into_iter());
|
||||
}
|
||||
|
||||
pub fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand])) {
|
||||
f(self.draw_cmds.lock().as_slice());
|
||||
}
|
||||
|
||||
pub fn font_load(&self, family: &str) -> u32 {
|
||||
let mut family_cache = self.font_families.lock();
|
||||
|
||||
if let Some(cached) = family_cache.get(family) {
|
||||
return *cached;
|
||||
}
|
||||
|
||||
let font = self.font_store.load_font(family);
|
||||
let mut loaded = self.loaded_fonts.write();
|
||||
let id = loaded.len() as u32;
|
||||
family_cache.insert(family.to_string(), id);
|
||||
loaded.push(font);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn text_layout_new(&self, font_id: u32, text: &str) -> u32 {
|
||||
let loaded = self.loaded_fonts.read();
|
||||
let font = loaded.get(font_id as usize).unwrap();
|
||||
let layout = font.shape(text);
|
||||
self.text_layouts.write().insert(layout) as u32
|
||||
}
|
||||
|
||||
pub fn text_layout_delete(&self, id: u32) {
|
||||
self.text_layouts.write().remove(id as usize);
|
||||
}
|
||||
|
||||
pub fn text_layout_get_bounds(&self, id: u32, dst: &mut Rect) {
|
||||
let src = self.text_layouts.read().get(id as usize).unwrap().bounds;
|
||||
let _ = std::mem::replace(dst, src);
|
||||
}
|
||||
|
||||
pub fn message_new(&self, data: Vec<u8>) -> u32 {
|
||||
let mut store = self.message_store.write();
|
||||
let id = store.insert(data) as u32;
|
||||
id
|
||||
}
|
||||
|
||||
pub fn message_free(&self, id: u32) {
|
||||
let mut store = self.message_store.write();
|
||||
store.remove(id as usize);
|
||||
}
|
||||
|
||||
pub fn message_get_len(&self, id: u32) -> u32 {
|
||||
self.message_store.read().get(id as usize).unwrap().len() as u32
|
||||
}
|
||||
|
||||
pub fn message_get_data(&self, id: u32, dst: &mut [u8]) {
|
||||
let store = self.message_store.read();
|
||||
let src = store.get(id as usize).unwrap();
|
||||
dst.copy_from_slice(src);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SDPX-License-Identifier: LGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use super::{Arc, Backend, Instance, PanelId};
|
||||
use crate::{DrawCommand, ScriptAbi, ScriptAbiImpl};
|
||||
use super::{Arc, Backend, Instance, PanelId, ScriptAbi};
|
||||
use crate::DrawCommand;
|
||||
|
||||
use canary_script::{Color, CursorEventKind, Rect, Vec2};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
type Caller<'a> = wasmtime::Caller<'a, ScriptAbiImpl>;
|
||||
type Store = wasmtime::Store<ScriptAbiImpl>;
|
||||
type Linker = wasmtime::Linker<ScriptAbiImpl>;
|
||||
type Caller<'a> = wasmtime::Caller<'a, ScriptAbi>;
|
||||
type Store = wasmtime::Store<ScriptAbi>;
|
||||
type Linker = wasmtime::Linker<ScriptAbi>;
|
||||
|
||||
pub struct WasmtimeBackend {
|
||||
engine: wasmtime::Engine,
|
||||
|
@ -30,9 +30,8 @@ impl WasmtimeBackend {
|
|||
}
|
||||
|
||||
impl Backend for WasmtimeBackend {
|
||||
fn load_module(&self, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>> {
|
||||
fn load_module(&self, abi: ScriptAbi, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>> {
|
||||
let module = wasmtime::Module::new(&self.engine, module)?;
|
||||
let abi = ScriptAbiImpl::default();
|
||||
let mut store = wasmtime::Store::new(&self.engine, abi);
|
||||
let mut linker = Linker::new(&self.engine);
|
||||
WasmtimeInstance::link(&mut linker)?;
|
||||
|
@ -62,7 +61,7 @@ impl Backend for WasmtimeBackend {
|
|||
|
||||
pub struct WasmtimeInstance {
|
||||
store: Mutex<Store>,
|
||||
bind_panel: wasmtime::TypedFunc<(u32, u32), u32>,
|
||||
bind_panel: wasmtime::TypedFunc<(u32, u32, u32), u32>,
|
||||
update: wasmtime::TypedFunc<(u32, f32), ()>,
|
||||
draw: wasmtime::TypedFunc<u32, ()>,
|
||||
on_resize: wasmtime::TypedFunc<(u32, f32, f32), ()>,
|
||||
|
@ -190,11 +189,13 @@ impl WasmtimeInstance {
|
|||
}
|
||||
|
||||
impl Instance for WasmtimeInstance {
|
||||
fn bind_panel(&self, panel: PanelId, msg: Vec<u8>) -> u32 {
|
||||
fn bind_panel(&self, panel: PanelId, protocol: &str, msg: Vec<u8>) -> u32 {
|
||||
let mut store = self.store.lock();
|
||||
let protocol = store.data().message_new(protocol.as_bytes().to_vec());
|
||||
let msg = store.data().message_new(msg);
|
||||
let args = (panel.0 as u32, msg);
|
||||
let args = (panel.0 as u32, protocol, msg);
|
||||
let data = self.bind_panel.call(store.deref_mut(), args).unwrap();
|
||||
store.data().message_free(protocol);
|
||||
store.data().message_free(msg);
|
||||
data
|
||||
}
|
||||
|
|
132
src/lib.rs
132
src/lib.rs
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SDPX-License-Identifier: LGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
pub use canary_script::*;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
|
@ -10,20 +10,26 @@ use std::sync::Arc;
|
|||
pub mod backend;
|
||||
pub mod text;
|
||||
|
||||
use backend::{Backend, Instance};
|
||||
use backend::{Backend, Instance, ScriptAbi};
|
||||
use text::FontStore;
|
||||
|
||||
/// The main interface to Canary.
|
||||
pub struct Runtime {
|
||||
backend: Box<dyn Backend>,
|
||||
font_store: Arc<FontStore>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn new(backend: Box<dyn Backend>) -> anyhow::Result<Self> {
|
||||
Ok(Self { backend })
|
||||
Ok(Self {
|
||||
backend,
|
||||
font_store: Arc::new(FontStore::new()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_module(&self, module: &[u8]) -> anyhow::Result<Script> {
|
||||
let instance = self.backend.load_module(module)?;
|
||||
let abi = ScriptAbi::new(self.font_store.to_owned());
|
||||
let instance = self.backend.load_module(abi, module)?;
|
||||
|
||||
Ok(Script {
|
||||
instance,
|
||||
|
@ -39,10 +45,10 @@ pub struct Script {
|
|||
}
|
||||
|
||||
impl Script {
|
||||
pub fn create_panel(&mut self, msg: Vec<u8>) -> anyhow::Result<Panel> {
|
||||
pub fn create_panel(&mut self, protocol: &str, msg: Vec<u8>) -> anyhow::Result<Panel> {
|
||||
let id = PanelId(self.next_panel);
|
||||
self.next_panel += 1;
|
||||
let userdata = self.instance.bind_panel(id, msg);
|
||||
let userdata = self.instance.bind_panel(id, protocol, msg);
|
||||
Ok(Panel {
|
||||
instance: self.instance.clone(),
|
||||
id,
|
||||
|
@ -83,28 +89,6 @@ impl Panel {
|
|||
/// Proportion constant between pixels (at 96dpi) to millimeters (Canary's unit measurement).
|
||||
pub const PX_PER_MM: f32 = 25.4 / 96.0;
|
||||
|
||||
/// Low-level script API callbacks.
|
||||
///
|
||||
/// If you're a casual user of Canary the struct you're looking for is
|
||||
/// [ScriptAbiImpl]. This trait exists to help with making mocks for testing.
|
||||
pub trait ScriptAbi {
|
||||
fn start_draw(&self);
|
||||
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]);
|
||||
fn draw_text_layout(&self, id: u32, offset: Vec2, scale: f32, color: Color);
|
||||
fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand]));
|
||||
|
||||
fn font_load(&self, family: &str) -> u32;
|
||||
|
||||
fn text_layout_new(&self, font_id: u32, text: &str) -> u32;
|
||||
fn text_layout_delete(&self, id: u32);
|
||||
fn text_layout_get_bounds(&self, id: u32, rect: &mut Rect);
|
||||
|
||||
fn message_new(&self, data: Vec<u8>) -> u32;
|
||||
fn message_free(&self, id: u32);
|
||||
fn message_get_len(&self, id: u32) -> u32;
|
||||
fn message_get_data(&self, id: u32, dst: &mut [u8]);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct PanelId(pub(crate) usize);
|
||||
|
||||
|
@ -116,95 +100,3 @@ pub enum DrawCommand {
|
|||
indices: Vec<MeshIndex>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The standard [ScriptAbi] implementation to use.
|
||||
#[derive(Default)]
|
||||
pub struct ScriptAbiImpl {
|
||||
draw_cmds: Mutex<Vec<DrawCommand>>,
|
||||
font_store: text::FontStore,
|
||||
font_families: Mutex<HashMap<String, u32>>,
|
||||
loaded_fonts: RwLock<Vec<Arc<text::Font>>>,
|
||||
text_layouts: RwLock<Slab<text::TextLayout>>,
|
||||
message_store: RwLock<Slab<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl ScriptAbi for ScriptAbiImpl {
|
||||
fn start_draw(&self) {
|
||||
let mut lock = self.draw_cmds.lock();
|
||||
lock.clear();
|
||||
}
|
||||
|
||||
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) {
|
||||
self.draw_cmds.lock().push(DrawCommand::Mesh {
|
||||
vertices: vertices.to_vec(),
|
||||
indices: indices.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
fn draw_text_layout(&self, id: u32, offset: Vec2, scale: f32, color: Color) {
|
||||
// TODO multiple fonts per layout
|
||||
let layouts = self.text_layouts.read();
|
||||
let layout = layouts.get(id as usize).unwrap();
|
||||
let glyphs = layout.glyphs.as_slice();
|
||||
let loaded = self.loaded_fonts.read();
|
||||
let font = loaded.get(layout.font_id as usize).unwrap();
|
||||
let cmds = font.draw(glyphs, offset, scale, color);
|
||||
self.draw_cmds.lock().extend(cmds.into_iter());
|
||||
}
|
||||
|
||||
fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand])) {
|
||||
f(self.draw_cmds.lock().as_slice());
|
||||
}
|
||||
|
||||
fn font_load(&self, family: &str) -> u32 {
|
||||
let mut family_cache = self.font_families.lock();
|
||||
|
||||
if let Some(cached) = family_cache.get(family) {
|
||||
return *cached;
|
||||
}
|
||||
|
||||
let font = self.font_store.load_font(family);
|
||||
let mut loaded = self.loaded_fonts.write();
|
||||
let id = loaded.len() as u32;
|
||||
family_cache.insert(family.to_string(), id);
|
||||
loaded.push(font);
|
||||
id
|
||||
}
|
||||
|
||||
fn text_layout_new(&self, font_id: u32, text: &str) -> u32 {
|
||||
let loaded = self.loaded_fonts.read();
|
||||
let font = loaded.get(font_id as usize).unwrap();
|
||||
let layout = font.shape(text);
|
||||
self.text_layouts.write().insert(layout) as u32
|
||||
}
|
||||
|
||||
fn text_layout_delete(&self, id: u32) {
|
||||
self.text_layouts.write().remove(id as usize);
|
||||
}
|
||||
|
||||
fn text_layout_get_bounds(&self, id: u32, dst: &mut Rect) {
|
||||
let src = self.text_layouts.read().get(id as usize).unwrap().bounds;
|
||||
let _ = std::mem::replace(dst, src);
|
||||
}
|
||||
|
||||
fn message_new(&self, data: Vec<u8>) -> u32 {
|
||||
let mut store = self.message_store.write();
|
||||
let id = store.insert(data) as u32;
|
||||
id
|
||||
}
|
||||
|
||||
fn message_free(&self, id: u32) {
|
||||
let mut store = self.message_store.write();
|
||||
store.remove(id as usize);
|
||||
}
|
||||
|
||||
fn message_get_len(&self, id: u32) -> u32 {
|
||||
self.message_store.read().get(id as usize).unwrap().len() as u32
|
||||
}
|
||||
|
||||
fn message_get_data(&self, id: u32, dst: &mut [u8]) {
|
||||
let store = self.message_store.read();
|
||||
let src = store.get(id as usize).unwrap();
|
||||
dst.copy_from_slice(src);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SDPX-License-Identifier: LGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
use super::{AllsortsFont, Rect, Vec2};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SDPX-License-Identifier: LGPL-3.0-or-later
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
use super::{Color, DrawCommand, MeshIndex, MeshVertex, Rect, Vec2};
|
||||
|
||||
|
|
Loading…
Reference in New Issue