streamdeck-workspace/streamdeck-gtk/src/widgets/command_row.rs
2022-03-16 19:13:56 -05:00

219 lines
7.3 KiB
Rust

use crate::{
command::CommandRowState,
daemon::{CommandState, Handle},
};
use futures_util::stream::StreamExt;
use gtk::{prelude::*, subclass::prelude::*};
use marble::widgets::Dialog;
use std::{cell::RefCell, rc::Rc};
use streamdeck_common::{Command, Input};
glib::wrapper! {
pub struct CommandRow(ObjectSubclass<imp::CommandRow>)
@extends gtk::ListBoxRow, gtk::Bin, gtk::Container, gtk::Widget;
}
impl CommandRow {
pub(crate) fn new(serial_number: &str, command_state: CommandState) -> Self {
let this = glib::Object::new(&[]).expect("Failed to create Command Row");
let inner = imp::CommandRow::from_instance(&this);
inner.serial_number.set(serial_number.to_owned()).unwrap();
let row_title = inner.row_title.get().unwrap();
if row_title.label() == "" {
row_title.set_label(&command_state.name());
}
let command_grid = inner.command_grid.get().unwrap();
let row_state = CommandRowState::build(&command_state.command(), command_grid);
inner.row_state.set(row_state).unwrap();
inner.input_key.set(command_state.keys()).unwrap();
inner
.command
.set(Rc::new(RefCell::new(command_state.command())))
.unwrap();
let stream = command_state.clone().name_stream();
let weak = this.downgrade();
glib::MainContext::default().spawn_local(async move {
futures_util::pin_mut!(stream);
while let Some(name) = stream.next().await {
if let Some(this) = weak.upgrade() {
this.set_name(&name);
} else {
break;
}
}
});
let stream = command_state.command_stream();
let weak = this.downgrade();
glib::MainContext::default().spawn_local(async move {
futures_util::pin_mut!(stream);
while let Some(command) = stream.next().await {
if let Some(this) = weak.upgrade() {
this.set_command(command);
} else {
break;
}
}
});
this.show_all();
this
}
pub(crate) fn set_command(&self, command: Command) {
let inner = imp::CommandRow::from_instance(self);
let row_state = inner.row_state.get().unwrap();
row_state.update_command(&command);
}
pub(crate) fn set_name(&self, name: &str) {
let this = imp::CommandRow::from_instance(self);
let row_title = this.row_title.get().unwrap();
row_title.set_label(name);
}
pub(crate) fn serial_number(&self) -> String {
let this = imp::CommandRow::from_instance(self);
let serial_number = this.serial_number.get().unwrap();
serial_number.clone()
}
pub(crate) fn input_keys(&self) -> Input {
let this = imp::CommandRow::from_instance(self);
let input_keys = this.input_key.get().unwrap();
input_keys.clone()
}
fn present_dialog(&self) {
let this = imp::CommandRow::from_instance(self);
let mut dialog_opt = this.dialog.borrow_mut();
let dialog: Dialog = if let Some(dialog) = &*dialog_opt {
dialog.clone()
} else {
let command_state = if let Some(deck_state) =
Handle::current().state().deck_state(&self.serial_number())
{
if let Some(command_state) = deck_state.command_state(self.input_keys()) {
command_state
} else {
return;
}
} else {
return;
};
let dialog =
crate::dialogs::edit_command_dialog::build(&self.serial_number(), command_state);
dialog.set_transient_for(
self.toplevel()
.and_then(|widget| widget.downcast::<crate::main_window::MainWindow>().ok())
.as_ref(),
);
*dialog_opt = Some(dialog.clone());
dialog.connect_response(glib::clone!(@weak self as obj => move |_, response_type| {
if response_type == gtk::ResponseType::Reject {
if let Some(main_window) = obj.toplevel().and_then(|widget| widget.downcast::<crate::main_window::MainWindow>().ok()) {
main_window.to_obs();
}
}
let this = imp::CommandRow::from_instance(&obj);
let mut dialog_opt = this.dialog.borrow_mut();
if let Some(dialog) = dialog_opt.take() {
dialog.close();
}
}));
dialog
};
dialog.present();
}
}
mod imp {
use crate::command::CommandRowState;
use gtk::{prelude::*, subclass::prelude::*};
use marble::widgets::Dialog;
use once_cell::unsync::OnceCell;
use std::{cell::RefCell, rc::Rc};
use streamdeck_common::{Command, Input};
#[derive(Debug, Default)]
pub struct CommandRow {
pub(super) row_title: OnceCell<gtk::Label>,
pub(super) command_grid: OnceCell<gtk::Grid>,
pub(super) serial_number: OnceCell<String>,
pub(super) input_key: OnceCell<Input>,
pub(super) command: OnceCell<Rc<RefCell<Command>>>,
pub(super) row_state: OnceCell<CommandRowState>,
pub(super) dialog: RefCell<Option<Dialog>>,
}
#[glib::object_subclass]
impl ObjectSubclass for CommandRow {
const NAME: &'static str = "CommandRow";
type Type = super::CommandRow;
type ParentType = gtk::ListBoxRow;
}
impl ObjectImpl for CommandRow {
fn constructed(&self, obj: &Self::Type) {
let row_title = gtk::Label::new(None);
row_title.set_halign(gtk::Align::Start);
row_title.set_valign(gtk::Align::Center);
let command_grid = gtk::Grid::new();
command_grid.set_halign(gtk::Align::Start);
command_grid.set_valign(gtk::Align::Center);
command_grid.set_orientation(gtk::Orientation::Horizontal);
command_grid.set_margin(0);
command_grid.set_margin_start(3);
command_grid.set_column_spacing(3);
let row_edit =
gtk::Button::from_icon_name(Some("document-edit"), gtk::IconSize::Button);
row_edit.set_tooltip_text(Some("Edit"));
row_edit.set_halign(gtk::Align::End);
row_edit.set_valign(gtk::Align::Center);
row_edit.set_expand(true);
let row_grid = gtk::Grid::new();
row_grid.set_orientation(gtk::Orientation::Horizontal);
row_grid.set_margin(6);
row_grid.set_margin_start(3);
row_grid.set_column_spacing(12);
row_grid.add(&row_title);
row_grid.add(&command_grid);
row_grid.add(&row_edit);
obj.add(&row_grid);
obj.show_all();
self.row_title.set(row_title).unwrap();
self.command_grid.set(command_grid).unwrap();
row_edit.connect_clicked(glib::clone!(@weak obj => move |_| {
obj.present_dialog();
}));
}
}
impl WidgetImpl for CommandRow {}
impl ContainerImpl for CommandRow {}
impl BinImpl for CommandRow {}
impl ListBoxRowImpl for CommandRow {}
}