Compare commits
2 commits
d27e12d9f8
...
bb1057e950
| Author | SHA1 | Date | |
|---|---|---|---|
| bb1057e950 | |||
| 74ae0e8a17 |
6 changed files with 500 additions and 35 deletions
146
src/main.rs
146
src/main.rs
|
|
@ -9,26 +9,30 @@ use cosmic::app::{Core, Settings, Task};
|
|||
use cosmic::iced::alignment::Vertical;
|
||||
use cosmic::iced::keyboard::{Key, Modifiers};
|
||||
use cosmic::iced::window::{Mode, Position};
|
||||
use cosmic::iced::{self, event, window, Length, Point};
|
||||
use cosmic::iced::{self, event, window, Color, Length, Point};
|
||||
use cosmic::iced_futures::Subscription;
|
||||
use cosmic::iced_runtime::dnd::DndAction;
|
||||
use cosmic::iced_widget::{column, row, stack};
|
||||
use cosmic::theme;
|
||||
use cosmic::widget::dnd_destination::dnd_destination;
|
||||
use cosmic::widget::menu::key_bind::Modifier;
|
||||
use cosmic::widget::menu::{ItemWidth, KeyBind};
|
||||
use cosmic::widget::nav_bar::nav_bar_style;
|
||||
use cosmic::widget::tooltip::Position as TPosition;
|
||||
use cosmic::widget::Container;
|
||||
use cosmic::widget::{
|
||||
button, horizontal_space, mouse_area, nav_bar, search_input,
|
||||
tooltip, vertical_space, Space,
|
||||
button, dnd_source, horizontal_space, mouse_area, nav_bar,
|
||||
search_input, tooltip, vertical_space, RcElementWrapper, Space,
|
||||
};
|
||||
use cosmic::widget::{container, text};
|
||||
use cosmic::widget::{icon, slider};
|
||||
use cosmic::widget::{menu, Container};
|
||||
use cosmic::{executor, Application, ApplicationExt, Element};
|
||||
use crisp::types::Value;
|
||||
use lisp::parse_lisp;
|
||||
use miette::{miette, Result};
|
||||
use rayon::prelude::*;
|
||||
use resvg::usvg::fontdb;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -119,6 +123,7 @@ struct App {
|
|||
search_id: cosmic::widget::Id,
|
||||
library_dragged_item: Option<ServiceItem>,
|
||||
fontdb: Arc<fontdb::Database>,
|
||||
menu_keys: HashMap<KeyBind, MenuAction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -147,6 +152,35 @@ enum Message {
|
|||
CloseSearch,
|
||||
UpdateSearchResults(Vec<ServiceItem>),
|
||||
OpenEditor(ServiceItem),
|
||||
New,
|
||||
Open,
|
||||
OpenFile(PathBuf),
|
||||
Save(Option<PathBuf>),
|
||||
SaveAs,
|
||||
OpenSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum MenuAction {
|
||||
New,
|
||||
Save,
|
||||
SaveAs,
|
||||
Open,
|
||||
OpenSettings,
|
||||
}
|
||||
|
||||
impl menu::Action for MenuAction {
|
||||
type Message = Message;
|
||||
|
||||
fn message(&self) -> Self::Message {
|
||||
match self {
|
||||
MenuAction::New => Message::New,
|
||||
MenuAction::Save => Message::Save(None),
|
||||
MenuAction::SaveAs => Message::SaveAs,
|
||||
MenuAction::Open => Message::Open,
|
||||
MenuAction::OpenSettings => Message::OpenSettings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HEADER_SPACE: u16 = 6;
|
||||
|
|
@ -230,6 +264,28 @@ impl cosmic::Application for App {
|
|||
// nav_model.insert().text(item.title()).data(item.clone());
|
||||
// }
|
||||
|
||||
let mut menu_keys = HashMap::new();
|
||||
menu_keys.insert(
|
||||
KeyBind {
|
||||
modifiers: vec![Modifier::Ctrl],
|
||||
key: Key::Character("s".into()),
|
||||
},
|
||||
MenuAction::Save,
|
||||
);
|
||||
menu_keys.insert(
|
||||
KeyBind {
|
||||
modifiers: vec![Modifier::Ctrl],
|
||||
key: Key::Character("o".into()),
|
||||
},
|
||||
MenuAction::Open,
|
||||
);
|
||||
menu_keys.insert(
|
||||
KeyBind {
|
||||
modifiers: vec![Modifier::Ctrl],
|
||||
key: Key::Character(".".into()),
|
||||
},
|
||||
MenuAction::OpenSettings,
|
||||
);
|
||||
// nav_model.activate_position(0);
|
||||
let mut app = Self {
|
||||
presenter,
|
||||
|
|
@ -251,6 +307,7 @@ impl cosmic::Application for App {
|
|||
current_item: (0, 0),
|
||||
library_dragged_item: None,
|
||||
fontdb: Arc::clone(&fontdb),
|
||||
menu_keys,
|
||||
};
|
||||
|
||||
let mut batch = vec![];
|
||||
|
|
@ -270,7 +327,46 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||
vec![]
|
||||
let file_menu = menu::Tree::with_children(
|
||||
Into::<Element<Message>>::into(menu::root("File")),
|
||||
menu::items(
|
||||
&self.menu_keys,
|
||||
vec![
|
||||
menu::Item::Button("New", None, MenuAction::New),
|
||||
menu::Item::Button(
|
||||
"Open",
|
||||
None,
|
||||
MenuAction::Open,
|
||||
),
|
||||
menu::Item::Button(
|
||||
"Save",
|
||||
None,
|
||||
MenuAction::Save,
|
||||
),
|
||||
menu::Item::Button(
|
||||
"Save As",
|
||||
None,
|
||||
MenuAction::SaveAs,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
let settings_menu = menu::Tree::with_children(
|
||||
Into::<Element<Message>>::into(menu::root("Settings")),
|
||||
menu::items(
|
||||
&self.menu_keys,
|
||||
vec![menu::Item::Button(
|
||||
"Open Settings",
|
||||
None,
|
||||
MenuAction::OpenSettings,
|
||||
)],
|
||||
),
|
||||
);
|
||||
let menu_bar =
|
||||
menu::bar::<Message>(vec![file_menu, settings_menu])
|
||||
.item_width(ItemWidth::Static(250))
|
||||
.main_offset(10);
|
||||
vec![menu_bar.into()]
|
||||
}
|
||||
|
||||
fn header_center(&self) -> Vec<Element<Self::Message>> {
|
||||
|
|
@ -978,6 +1074,34 @@ impl cosmic::Application for App {
|
|||
ServiceItemKind::Content(_slide) => todo!(),
|
||||
}
|
||||
}
|
||||
Message::New => {
|
||||
debug!("new file");
|
||||
Task::none()
|
||||
}
|
||||
Message::Open => {
|
||||
debug!("Open file");
|
||||
Task::none()
|
||||
}
|
||||
Message::OpenFile(file) => {
|
||||
debug!(?file, "opening file");
|
||||
Task::none()
|
||||
}
|
||||
Message::Save(file) => {
|
||||
let Some(file) = file else {
|
||||
debug!("saving current");
|
||||
return Task::none();
|
||||
};
|
||||
debug!(?file, "saving new file");
|
||||
Task::none()
|
||||
}
|
||||
Message::SaveAs => {
|
||||
debug!("saving as a file");
|
||||
Task::none()
|
||||
}
|
||||
Message::OpenSettings => {
|
||||
debug!("Opening settings");
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1237,6 +1361,15 @@ where
|
|||
}
|
||||
}
|
||||
match (key, modifiers) {
|
||||
(Key::Character(k), Modifiers::CTRL) if k == *"s" => {
|
||||
self.update(Message::Save(None))
|
||||
}
|
||||
(Key::Character(k), Modifiers::CTRL) if k == *"o" => {
|
||||
self.update(Message::Open)
|
||||
}
|
||||
(Key::Character(k), Modifiers::CTRL) if k == *"." => {
|
||||
self.update(Message::OpenSettings)
|
||||
}
|
||||
(Key::Character(k), Modifiers::CTRL)
|
||||
if k == *"k" || k == *"f" =>
|
||||
{
|
||||
|
|
@ -1306,8 +1439,7 @@ where
|
|||
// .icon_size(cosmic::theme::spacing().space_l)
|
||||
.class(cosmic::theme::style::Button::HeaderBar)
|
||||
// .spacing(cosmic::theme::spacing().space_l)
|
||||
// .padding(cosmic::theme::spacing().space_m)
|
||||
// .height(cosmic::theme::spacing().space_xxxl)
|
||||
.height(cosmic::theme::spacing().space_xl)
|
||||
.width(Length::Fill)
|
||||
.on_press(Message::ChangeServiceItem(index));
|
||||
let tooltip = tooltip(button,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::core::model::LibraryKind;
|
|||
pub mod double_ended_slider;
|
||||
pub mod library;
|
||||
pub mod presenter;
|
||||
pub mod service;
|
||||
pub mod slide_editor;
|
||||
pub mod song_editor;
|
||||
pub mod text_svg;
|
||||
|
|
|
|||
|
|
@ -399,21 +399,11 @@ impl Presenter {
|
|||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
slide_view(
|
||||
self.current_slide.clone(),
|
||||
&self.video,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
slide_view(&self.current_slide, &self.video, false, true)
|
||||
}
|
||||
|
||||
pub fn view_preview(&self) -> Element<Message> {
|
||||
slide_view(
|
||||
self.current_slide.clone(),
|
||||
&self.video,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
slide_view(&self.current_slide, &self.video, false, false)
|
||||
}
|
||||
|
||||
pub fn preview_bar(&self) -> Element<Message> {
|
||||
|
|
@ -423,19 +413,6 @@ impl Presenter {
|
|||
let mut slides = vec![];
|
||||
item.slides.iter().enumerate().for_each(
|
||||
|(slide_index, slide)| {
|
||||
let font_name = slide.font().into_boxed_str();
|
||||
let family =
|
||||
Family::Name(Box::leak(font_name));
|
||||
let weight = Weight::Normal;
|
||||
let stretch = Stretch::Normal;
|
||||
let style = Style::Normal;
|
||||
let font = Font {
|
||||
family,
|
||||
weight,
|
||||
stretch,
|
||||
style,
|
||||
};
|
||||
|
||||
let is_current_slide =
|
||||
(item_index, slide_index)
|
||||
== (
|
||||
|
|
@ -444,7 +421,7 @@ impl Presenter {
|
|||
);
|
||||
|
||||
let container = slide_view(
|
||||
slide.clone(),
|
||||
&slide,
|
||||
&self.video,
|
||||
true,
|
||||
false,
|
||||
|
|
@ -705,7 +682,7 @@ fn scale_font(font_size: f32, width: f32) -> f32 {
|
|||
}
|
||||
|
||||
pub(crate) fn slide_view<'a>(
|
||||
slide: Slide,
|
||||
slide: &'a Slide,
|
||||
video: &'a Option<Video>,
|
||||
delegate: bool,
|
||||
hide_mouse: bool,
|
||||
|
|
|
|||
351
src/ui/service.rs
Normal file
351
src/ui/service.rs
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
use std::any::Any;
|
||||
|
||||
use cosmic::iced::{self, Size};
|
||||
use cosmic::iced_core::window;
|
||||
|
||||
use cosmic::{
|
||||
iced::{
|
||||
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
||||
event, mouse, overlay, Event, Length, Point, Rectangle,
|
||||
Vector,
|
||||
},
|
||||
iced_core::{
|
||||
self, layout, renderer,
|
||||
widget::{tree, Tree},
|
||||
Clipboard, Shell,
|
||||
},
|
||||
widget::{container, Id, Widget},
|
||||
Element,
|
||||
};
|
||||
|
||||
use crate::core::service_items::ServiceItem;
|
||||
|
||||
pub fn service<'a, Message: Clone + 'static>(
|
||||
service: &'a Vec<ServiceItem>,
|
||||
) -> Service<'a, Message> {
|
||||
Service::new(service)
|
||||
}
|
||||
|
||||
pub struct Service<'a, Message> {
|
||||
service: &'a Vec<ServiceItem>,
|
||||
on_start: Option<Message>,
|
||||
on_cancelled: Option<Message>,
|
||||
on_finish: Option<Message>,
|
||||
drag_threshold: f32,
|
||||
width: Length,
|
||||
height: Length,
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> Service<'a, Message> {
|
||||
pub fn new(service: &'a Vec<ServiceItem>) -> Self {
|
||||
Self {
|
||||
service,
|
||||
drag_threshold: 8.0,
|
||||
on_start: None,
|
||||
on_cancelled: None,
|
||||
on_finish: None,
|
||||
width: Length::Fill,
|
||||
height: Length::Fill,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn drag_threshold(mut self, threshold: f32) -> Self {
|
||||
self.drag_threshold = threshold;
|
||||
self
|
||||
}
|
||||
|
||||
// pub fn start_dnd(
|
||||
// &self,
|
||||
// clipboard: &mut dyn Clipboard,
|
||||
// bounds: Rectangle,
|
||||
// offset: Vector,
|
||||
// ) {
|
||||
// let Some(content) = self.drag_content.as_ref().map(|f| f())
|
||||
// else {
|
||||
// return;
|
||||
// };
|
||||
|
||||
// iced_core::clipboard::start_dnd(
|
||||
// clipboard,
|
||||
// false,
|
||||
// if let Some(window) = self.window.as_ref() {
|
||||
// Some(iced_core::clipboard::DndSource::Surface(
|
||||
// *window,
|
||||
// ))
|
||||
// } else {
|
||||
// Some(iced_core::clipboard::DndSource::Widget(
|
||||
// self.id.clone(),
|
||||
// ))
|
||||
// },
|
||||
// self.drag_icon.as_ref().map(|f| {
|
||||
// let (icon, state, offset) = f(offset);
|
||||
// iced_core::clipboard::IconSurface::new(
|
||||
// container(icon)
|
||||
// .width(Length::Fixed(bounds.width))
|
||||
// .height(Length::Fixed(bounds.height))
|
||||
// .into(),
|
||||
// state,
|
||||
// offset,
|
||||
// )
|
||||
// }),
|
||||
// Box::new(content),
|
||||
// DndAction::Move,
|
||||
// );
|
||||
// }
|
||||
|
||||
pub fn on_start(mut self, on_start: Option<Message>) -> Self {
|
||||
self.on_start = on_start;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_cancel(
|
||||
mut self,
|
||||
on_cancelled: Option<Message>,
|
||||
) -> Self {
|
||||
self.on_cancelled = on_cancelled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
|
||||
self.on_finish = on_finish;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message: Clone + 'static>
|
||||
Widget<Message, cosmic::Theme, cosmic::Renderer>
|
||||
for Service<'_, Message>
|
||||
{
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
Size {
|
||||
width: Length::Fill,
|
||||
height: Length::Fill,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut Tree,
|
||||
_renderer: &cosmic::Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout::atomic(limits, self.width, self.height)
|
||||
}
|
||||
|
||||
// fn operate(
|
||||
// &self,
|
||||
// tree: &mut Tree,
|
||||
// layout: layout::Layout<'_>,
|
||||
// renderer: &cosmic::Renderer,
|
||||
// operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
// ) {
|
||||
// operation.custom(
|
||||
// (&mut tree.state) as &mut dyn Any,
|
||||
// Some(&self.id),
|
||||
// );
|
||||
// operation.container(
|
||||
// Some(&self.id),
|
||||
// layout.bounds(),
|
||||
// &mut |operation| {
|
||||
// self.container.as_widget().operate(
|
||||
// &mut tree.children[0],
|
||||
// layout,
|
||||
// renderer,
|
||||
// operation,
|
||||
// )
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: layout::Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &cosmic::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse_event) => match mouse_event {
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
||||
if let Some(position) = cursor.position() {
|
||||
if !state.hovered {
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
|
||||
state.left_pressed_position = Some(position);
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left)
|
||||
if state.left_pressed_position.is_some() =>
|
||||
{
|
||||
state.left_pressed_position = None;
|
||||
return event::Status::Captured;
|
||||
}
|
||||
mouse::Event::CursorMoved { .. } => {
|
||||
if let Some(position) = cursor.position() {
|
||||
if state.hovered {
|
||||
// We ignore motion if we do not possess drag content by now.
|
||||
if let Some(left_pressed_position) =
|
||||
state.left_pressed_position
|
||||
{
|
||||
if position
|
||||
.distance(left_pressed_position)
|
||||
> self.drag_threshold
|
||||
{
|
||||
if let Some(on_start) =
|
||||
self.on_start.as_ref()
|
||||
{
|
||||
shell
|
||||
.publish(on_start.clone())
|
||||
}
|
||||
let offset = Vector::new(
|
||||
left_pressed_position.x
|
||||
- layout.bounds().x,
|
||||
left_pressed_position.y
|
||||
- layout.bounds().y,
|
||||
);
|
||||
state.is_dragging = true;
|
||||
state.left_pressed_position =
|
||||
None;
|
||||
}
|
||||
}
|
||||
if !cursor.is_over(layout.bounds()) {
|
||||
state.hovered = false;
|
||||
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
} else if cursor.is_over(layout.bounds()) {
|
||||
state.hovered = true;
|
||||
}
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
_ => return event::Status::Ignored,
|
||||
},
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
|
||||
if state.is_dragging {
|
||||
if let Some(m) = self.on_cancelled.as_ref() {
|
||||
shell.publish(m.clone());
|
||||
}
|
||||
state.is_dragging = false;
|
||||
return event::Status::Captured;
|
||||
}
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
|
||||
if state.is_dragging {
|
||||
if let Some(m) = self.on_finish.as_ref() {
|
||||
shell.publish(m.clone());
|
||||
}
|
||||
state.is_dragging = false;
|
||||
return event::Status::Captured;
|
||||
}
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
_ => return event::Status::Ignored,
|
||||
}
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
// fn mouse_interaction(
|
||||
// &self,
|
||||
// tree: &Tree,
|
||||
// layout: layout::Layout<'_>,
|
||||
// cursor_position: mouse::Cursor,
|
||||
// viewport: &Rectangle,
|
||||
// renderer: &cosmic::Renderer,
|
||||
// ) -> mouse::Interaction {
|
||||
// let state = tree.state.downcast_ref::<State>();
|
||||
// if state.is_dragging {
|
||||
// return mouse::Interaction::Grabbing;
|
||||
// }
|
||||
// self.container.as_widget().mouse_interaction(
|
||||
// &tree.children[0],
|
||||
// layout,
|
||||
// cursor_position,
|
||||
// viewport,
|
||||
// renderer,
|
||||
// )
|
||||
// }
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut cosmic::Renderer,
|
||||
theme: &cosmic::Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: layout::Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
for item in self.service {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// fn overlay<'b>(
|
||||
// &'b mut self,
|
||||
// tree: &'b mut Tree,
|
||||
// layout: layout::Layout<'_>,
|
||||
// renderer: &cosmic::Renderer,
|
||||
// translation: Vector,
|
||||
// ) -> Option<
|
||||
// overlay::Element<
|
||||
// 'b,
|
||||
// Message,
|
||||
// cosmic::Theme,
|
||||
// cosmic::Renderer,
|
||||
// >,
|
||||
// > {
|
||||
// self.container.as_widget_mut().overlay(
|
||||
// &mut tree.children[0],
|
||||
// layout,
|
||||
// renderer,
|
||||
// translation,
|
||||
// )
|
||||
// }
|
||||
|
||||
// #[cfg(feature = "a11y")]
|
||||
// /// get the a11y nodes for the widget
|
||||
// fn a11y_nodes(
|
||||
// &self,
|
||||
// layout: iced_core::Layout<'_>,
|
||||
// state: &Tree,
|
||||
// p: mouse::Cursor,
|
||||
// ) -> iced_accessibility::A11yTree {
|
||||
// let c_state = &state.children[0];
|
||||
// self.container.as_widget().a11y_nodes(layout, c_state, p)
|
||||
// }
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<Service<'a, Message>>
|
||||
for Element<'a, Message>
|
||||
{
|
||||
fn from(e: Service<'a, Message>) -> Element<'a, Message> {
|
||||
Element::new(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Local state of the [`MouseListener`].
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
hovered: bool,
|
||||
left_pressed_position: Option<Point>,
|
||||
is_dragging: bool,
|
||||
cached_bounds: Rectangle,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -314,7 +314,7 @@ impl SongEditor {
|
|||
.map(|(index, slide)| {
|
||||
container(
|
||||
slide_view(
|
||||
slide.clone(),
|
||||
&slide,
|
||||
if index == 0 {
|
||||
&self.video
|
||||
} else {
|
||||
|
|
|
|||
4
todo.org
4
todo.org
|
|
@ -1,6 +1,9 @@
|
|||
#+TITLE: The Task list for Lumina
|
||||
|
||||
|
||||
* TODO [#A] Add removal and reordering of service_items
|
||||
* TODO Add OBS integration
|
||||
This will be based on each slide having the ability to activate an OBS scene when it is active.
|
||||
* TODO Move text_generation function to be asynchronous so that UI doesn't lock up during song editing.
|
||||
* TODO Build a video editor
|
||||
* TODO Build an image editor
|
||||
|
|
@ -73,6 +76,7 @@ This needs lots more attention
|
|||
This will make it so that we can add styling to the text like borders and backgrounds or highlights. Maybe in the future it'll add shadows too.
|
||||
* DONE Check into =mupdf-rs= for loading PDF's.
|
||||
|
||||
* DONE Build Menu
|
||||
* DONE Find a way for text to pass through a service item to a slide i.e. content piece
|
||||
This proved easier by just creating the =Slide= first and inserting it into the =ServiceItem=.
|
||||
* DONE [#A] Change return type of all components to an Action enum instead of the Task<Message> type [0%] [0/0]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue