/* Copyright 2023, Savanni D'Gerinel This file is part of Screenplay. Screenplay is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Screenplay is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Lumeto. If not, see . */ /// Screenplay is a library that helps visualize GTK scenes. I got my inspiration from working with /// Storybook and decided that I wanted a similar tool for GTK. /// /// With this library, you can write an application that loads up a bunch of different user /// interfaces, and the library will put them on separate pages. Each UI can have as much or as /// little behavior as you want. use gtk::prelude::*; #[derive(Clone, Debug)] enum Action { SelectPage(usize), Deselect, } /// Unused. In the near future, this will contain useful errors. #[derive(Clone, Debug)] pub struct Error; /// A definition of a single Screen. The screen can be as much or as little as you want, but must /// contain a title (for the page selector) and a widget to show the screen. #[derive(Clone)] pub struct Screen { /// Title of the screen. This will only be rendered in the page selector. pub title: String, /// The screen itself. This can be anything that implements the gtk::Widget interface, and it /// will be rendered in the main area of the window. pub widget: gtk::Widget, /// Unused today, however... /// These will be rendered in an area of the window reserved for adjustments. They are meant to /// be ways to provide variants to the current scene. You will need to attach behaviors. pub adjustments: Vec, } /// Screenplay is an object that will render a gtk::ApplicationWindow. /// /// That window contains a sidebar which lists the names of all of the Screens that have been /// provided to it. Selecting one of those will cause the application to render that screen and all /// of its adjustments. /// /// It is highly likely that in the future this structure will be reformulated as a child of a GTK /// ApplicationWindow. #[derive(Clone)] pub struct Screenplay { frame: gtk::Frame, screens: Vec, } impl Screenplay { /// Construct a new Screenplay. This will construct and render the ApplicationWindow as the /// primary child of the Application. /// /// This function currently returns no errors, instead panicing if anything goes wrong. pub fn new(gtk_app: >k::Application, screens: Vec) -> Result { let window = gtk::ApplicationWindow::new(gtk_app); window.show(); let (sender, receiver) = gtk::glib::MainContext::channel(gtk::glib::PRIORITY_DEFAULT); let layout = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); window.set_child(Some(&layout)); let listbox = gtk::ListBox::builder() .activate_on_single_click(true) .show_separators(true) .focusable(false) .build(); let frame = gtk::Frame::builder() .height_request(500) .width_request(500) .build(); layout.append(&listbox); layout.append(&frame); listbox.connect_row_activated(move |_, row| { match row.index() { -1 => sender.send(Action::Deselect), idx => sender.send(Action::SelectPage(idx as usize)), } .unwrap() }); screens.iter().for_each(|Screen { title, .. }| { listbox.append( >k::ListBoxRow::builder() .child(>k::Label::new(Some(title.as_ref()))) .build(), ); }); let storybook = Self { frame, screens }; { let mut storybook = storybook.clone(); receiver.attach(None, move |message| storybook.process_action(message)); } Ok(storybook) } /// Handle an application action. This generally means changing the current screen. fn process_action(&mut self, message: Action) -> Continue { let nothing: Option<>k::Widget> = None; match message { Action::SelectPage(index) => match self.screens.get(index) { Some(Screen { widget, adjustments, .. }) => { let b = gtk::Box::new(gtk::Orientation::Vertical, 0); let control_box = gtk::Box::new(gtk::Orientation::Vertical, 0); b.append(widget); b.append(&control_box); adjustments .iter() .for_each(|control| control_box.append(control)); self.frame.set_child(Some(&b)); } None => self.frame.set_child(nothing), }, Action::Deselect => self.frame.set_child(nothing), } Continue(true) } }