From 7679fcc3a0c4fadea00a1a320938851b1518028d Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Wed, 26 Jul 2023 09:42:17 +0200 Subject: add viewmanager --- src/gui/circular.rs | 421 ---------------------------------------------------- src/gui/easing.rs | 102 ------------- src/gui/generate.rs | 35 +++++ src/gui/mod.rs | 327 +++++----------------------------------- src/gui/state.rs | 51 +++++++ src/gui/welcome.rs | 44 ++++++ 6 files changed, 166 insertions(+), 814 deletions(-) delete mode 100644 src/gui/circular.rs delete mode 100644 src/gui/easing.rs create mode 100644 src/gui/generate.rs create mode 100644 src/gui/state.rs create mode 100644 src/gui/welcome.rs diff --git a/src/gui/circular.rs b/src/gui/circular.rs deleted file mode 100644 index 3a35e02..0000000 --- a/src/gui/circular.rs +++ /dev/null @@ -1,421 +0,0 @@ -//! Show a circular progress indicator. -use iced::advanced::layout; -use iced::advanced::renderer; -use iced::advanced::widget::tree::{self, Tree}; -use iced::advanced::{Clipboard, Layout, Renderer, Shell, Widget}; -use iced::event; -use iced::mouse; -use iced::time::Instant; -use iced::widget::canvas; -use iced::window::{self, RedrawRequest}; -use iced::{ - Background, Color, Element, Event, Length, Rectangle, Size, Vector, -}; - -use super::easing::{self, Easing}; - -use std::f32::consts::PI; -use std::time::Duration; - -const MIN_RADIANS: f32 = PI / 8.0; -const WRAP_RADIANS: f32 = 2.0 * PI - PI / 4.0; -const BASE_ROTATION_SPEED: u32 = u32::MAX / 80; - -#[allow(missing_debug_implementations)] -pub struct Circular<'a, Theme> -where - Theme: StyleSheet, -{ - size: f32, - bar_height: f32, - style: ::Style, - easing: &'a Easing, - cycle_duration: Duration, - rotation_duration: Duration, -} - -impl<'a, Theme> Circular<'a, Theme> -where - Theme: StyleSheet, -{ - /// Creates a new [`Circular`] with the given content. - pub fn new() -> Self { - Circular { - size: 40.0, - bar_height: 4.0, - style: ::Style::default(), - easing: &easing::STANDARD, - cycle_duration: Duration::from_millis(600), - rotation_duration: Duration::from_secs(2), - } - } - - /// Sets the size of the [`Circular`]. - pub fn size(mut self, size: f32) -> Self { - self.size = size; - self - } - - /// Sets the bar height of the [`Circular`]. - pub fn bar_height(mut self, bar_height: f32) -> Self { - self.bar_height = bar_height; - self - } - - /// Sets the style variant of this [`Circular`]. - pub fn style(mut self, style: ::Style) -> Self { - self.style = style; - self - } - - /// Sets the easing of this [`Circular`]. - pub fn easing(mut self, easing: &'a Easing) -> Self { - self.easing = easing; - self - } - - /// Sets the cycle duration of this [`Circular`]. - pub fn cycle_duration(mut self, duration: Duration) -> Self { - self.cycle_duration = duration / 2; - self - } - - /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full - /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting) - pub fn rotation_duration(mut self, duration: Duration) -> Self { - self.rotation_duration = duration; - self - } -} - -impl<'a, Theme> Default for Circular<'a, Theme> -where - Theme: StyleSheet, -{ - fn default() -> Self { - Self::new() - } -} - -#[derive(Clone, Copy)] -enum Animation { - Expanding { - start: Instant, - progress: f32, - rotation: u32, - last: Instant, - }, - Contracting { - start: Instant, - progress: f32, - rotation: u32, - last: Instant, - }, -} - -impl Default for Animation { - fn default() -> Self { - Self::Expanding { - start: Instant::now(), - progress: 0.0, - rotation: 0, - last: Instant::now(), - } - } -} - -impl Animation { - fn next(&self, additional_rotation: u32, now: Instant) -> Self { - match self { - Self::Expanding { rotation, .. } => Self::Contracting { - start: now, - progress: 0.0, - rotation: rotation.wrapping_add(additional_rotation), - last: now, - }, - Self::Contracting { rotation, .. } => Self::Expanding { - start: now, - progress: 0.0, - rotation: rotation.wrapping_add( - BASE_ROTATION_SPEED.wrapping_add( - ((WRAP_RADIANS / (2.0 * PI)) * u32::MAX as f32) as u32, - ), - ), - last: now, - }, - } - } - - fn start(&self) -> Instant { - match self { - Self::Expanding { start, .. } | Self::Contracting { start, .. } => { - *start - } - } - } - - fn last(&self) -> Instant { - match self { - Self::Expanding { last, .. } | Self::Contracting { last, .. } => { - *last - } - } - } - - fn timed_transition( - &self, - cycle_duration: Duration, - rotation_duration: Duration, - now: Instant, - ) -> Self { - let elapsed = now.duration_since(self.start()); - let additional_rotation = ((now - self.last()).as_secs_f32() - / rotation_duration.as_secs_f32() - * (u32::MAX) as f32) as u32; - - match elapsed { - elapsed if elapsed > cycle_duration => { - self.next(additional_rotation, now) - } - _ => self.with_elapsed( - cycle_duration, - additional_rotation, - elapsed, - now, - ), - } - } - - fn with_elapsed( - &self, - cycle_duration: Duration, - additional_rotation: u32, - elapsed: Duration, - now: Instant, - ) -> Self { - let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); - match self { - Self::Expanding { - start, rotation, .. - } => Self::Expanding { - start: *start, - progress, - rotation: rotation.wrapping_add(additional_rotation), - last: now, - }, - Self::Contracting { - start, rotation, .. - } => Self::Contracting { - start: *start, - progress, - rotation: rotation.wrapping_add(additional_rotation), - last: now, - }, - } - } - - fn rotation(&self) -> f32 { - match self { - Self::Expanding { rotation, .. } - | Self::Contracting { rotation, .. } => { - *rotation as f32 / u32::MAX as f32 - } - } - } -} - -#[derive(Default)] -struct State { - animation: Animation, - cache: canvas::Cache, -} - -impl<'a, Message, Theme> Widget> - for Circular<'a, Theme> -where - Message: 'a + Clone, - Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State::default()) - } - - fn width(&self) -> Length { - Length::Fixed(self.size) - } - - fn height(&self) -> Length { - Length::Fixed(self.size) - } - - fn layout( - &self, - _renderer: &iced::Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(self.size).height(self.size); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - _layout: Layout<'_>, - _cursor: mouse::Cursor, - _renderer: &iced::Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - const FRAME_RATE: u64 = 60; - - let state = tree.state.downcast_mut::(); - - if let Event::Window(window::Event::RedrawRequested(now)) = event { - state.animation = state.animation.timed_transition( - self.cycle_duration, - self.rotation_duration, - now, - ); - - state.cache.clear(); - shell.request_redraw(RedrawRequest::At( - now + Duration::from_millis(1000 / FRAME_RATE), - )); - } - - event::Status::Ignored - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut iced::Renderer, - theme: &Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor: mouse::Cursor, - _viewport: &Rectangle, - ) { - let state = tree.state.downcast_ref::(); - let bounds = layout.bounds(); - let custom_style = - ::appearance(theme, &self.style); - - let geometry = state.cache.draw(renderer, bounds.size(), |frame| { - let track_radius = frame.width() / 2.0 - self.bar_height; - let track_path = canvas::Path::circle(frame.center(), track_radius); - - frame.stroke( - &track_path, - canvas::Stroke::default() - .with_color(custom_style.track_color) - .with_width(self.bar_height), - ); - - let mut builder = canvas::path::Builder::new(); - - let start = state.animation.rotation() * 2.0 * PI; - - match state.animation { - Animation::Expanding { progress, .. } => { - builder.arc(canvas::path::Arc { - center: frame.center(), - radius: track_radius, - start_angle: start, - end_angle: start - + MIN_RADIANS - + WRAP_RADIANS * (self.easing.y_at_x(progress)), - }); - } - Animation::Contracting { progress, .. } => { - builder.arc(canvas::path::Arc { - center: frame.center(), - radius: track_radius, - start_angle: start - + WRAP_RADIANS * (self.easing.y_at_x(progress)), - end_angle: start + MIN_RADIANS + WRAP_RADIANS, - }); - } - } - - let bar_path = builder.build(); - - frame.stroke( - &bar_path, - canvas::Stroke::default() - .with_color(custom_style.bar_color) - .with_width(self.bar_height), - ); - }); - - renderer.with_translation( - Vector::new(bounds.x, bounds.y), - |renderer| { - use iced::advanced::graphics::geometry::Renderer as _; - - renderer.draw(vec![geometry]); - }, - ); - } -} - -impl<'a, Message, Theme> From> - for Element<'a, Message, iced::Renderer> -where - Message: Clone + 'a, - Theme: StyleSheet + 'a, -{ - fn from(circular: Circular<'a, Theme>) -> Self { - Self::new(circular) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct Appearance { - /// The [`Background`] of the progress indicator. - pub background: Option, - /// The track [`Color`] of the progress indicator. - pub track_color: Color, - /// The bar [`Color`] of the progress indicator. - pub bar_color: Color, -} - -impl std::default::Default for Appearance { - fn default() -> Self { - Self { - background: None, - track_color: Color::TRANSPARENT, - bar_color: Color::BLACK, - } - } -} - -/// A set of rules that dictate the style of an indicator. -pub trait StyleSheet { - /// The supported style of the [`StyleSheet`]. - type Style: Default; - - /// Produces the active [`Appearance`] of a indicator. - fn appearance(&self, style: &Self::Style) -> Appearance; -} - -impl StyleSheet for iced::Theme { - type Style = (); - - fn appearance(&self, _style: &Self::Style) -> Appearance { - let palette = self.extended_palette(); - - Appearance { - background: None, - track_color: palette.background.weak.color, - bar_color: palette.primary.base.color, - } - } -} diff --git a/src/gui/easing.rs b/src/gui/easing.rs deleted file mode 100644 index a4ef652..0000000 --- a/src/gui/easing.rs +++ /dev/null @@ -1,102 +0,0 @@ -use iced::Point; - -use lyon_algorithms::measure::PathMeasurements; -use lyon_algorithms::path::{builder::NoAttributes, path::BuilderImpl, Path}; -use once_cell::sync::Lazy; - -pub static STANDARD: Lazy = Lazy::new(|| { - Easing::builder() - .cubic_bezier_to([0.2, 0.0], [0.0, 1.0], [1.0, 1.0]) - .build() -}); - -pub struct Easing { - path: Path, - measurements: PathMeasurements, -} - -impl Easing { - pub fn builder() -> Builder { - Builder::new() - } - - pub fn y_at_x(&self, x: f32) -> f32 { - let mut sampler = self.measurements.create_sampler( - &self.path, - lyon_algorithms::measure::SampleType::Normalized, - ); - let sample = sampler.sample(x); - - sample.position().y - } -} - -pub struct Builder(NoAttributes); - -impl Builder { - pub fn new() -> Self { - let mut builder = Path::builder(); - builder.begin(lyon_algorithms::geom::point(0.0, 0.0)); - - Self(builder) - } - - /// Adds a line segment. Points must be between 0,0 and 1,1 - pub fn line_to(mut self, to: impl Into) -> Self { - self.0.line_to(Self::point(to)); - - self - } - - /// Adds a quadratic bézier curve. Points must be between 0,0 and 1,1 - pub fn quadratic_bezier_to( - mut self, - ctrl: impl Into, - to: impl Into, - ) -> Self { - self.0 - .quadratic_bezier_to(Self::point(ctrl), Self::point(to)); - - self - } - - /// Adds a cubic bézier curve. Points must be between 0,0 and 1,1 - pub fn cubic_bezier_to( - mut self, - ctrl1: impl Into, - ctrl2: impl Into, - to: impl Into, - ) -> Self { - self.0.cubic_bezier_to( - Self::point(ctrl1), - Self::point(ctrl2), - Self::point(to), - ); - - self - } - - pub fn build(mut self) -> Easing { - self.0.line_to(lyon_algorithms::geom::point(1.0, 1.0)); - self.0.end(false); - - let path = self.0.build(); - let measurements = PathMeasurements::from_path(&path, 0.0); - - Easing { path, measurements } - } - - fn point(p: impl Into) -> lyon_algorithms::geom::Point { - let p: Point = p.into(); - lyon_algorithms::geom::point( - p.x.min(1.0).max(0.0), - p.y.min(1.0).max(0.0), - ) - } -} - -impl Default for Builder { - fn default() -> Self { - Self::new() - } -} diff --git a/src/gui/generate.rs b/src/gui/generate.rs new file mode 100644 index 0000000..9fe9e3f --- /dev/null +++ b/src/gui/generate.rs @@ -0,0 +1,35 @@ +use gtk::prelude::*; +use super::state::{View, ViewManager}; +use std::rc::Rc; + +pub struct Generate { + vm : Rc +} + +impl View for Generate { + fn name(&self) -> &str { + "generate" + } + + fn set_vm(&mut self, vm : Rc) { + self.vm = vm + } + + fn make_current(&self) -> gtk::Box { + let center = gtk::Box::new(gtk::Orientation::Vertical, 10); + let main = gtk::Box::new(gtk::Orientation::Horizontal, 10); + let label = gtk::Label::new(Some("Generate")); + + main.pack_start(&label, false, false, 0); + + center.pack_start(&main, true, false, 0); + + center + } +} + +impl Generate { + pub fn new() -> Self { + Self { vm : Rc::new(ViewManager::empty()) } + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 593eb65..68f8ab3 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,295 +1,40 @@ -use std::fs::File; -use std::io::Write; -use std::thread; -use std::time::Duration; -use iced::theme::Theme; -use iced::widget::{text, row, scrollable, container, column, button, text_input, progress_bar}; -use iced::{window, Length}; -use iced::{Application, Element}; -use iced::{Command, Settings}; -use once_cell::sync::Lazy; use std::sync::Mutex; - -use crate::index::Index; -use crate::searchresult::SearchResult; -use crate::splitter; -use crate::index::GenState; - -mod easing; -mod circular; - -static SEARCH_ID: Lazy = Lazy::new(text_input::Id::unique); -static GENERATE_PROGRESS : Lazy> = Lazy::new(|| { Mutex::new(0) }); -static GENERATE_STATUS : Lazy> = Lazy::new(|| { Mutex::new(GenState::Fetching) }); - -pub fn run() -> iced::Result { - App::run(Settings { - window: window::Settings { - size: (1000, 800), - ..window::Settings::default() - }, - ..Settings::default() - }) -} - -#[derive(Debug)] -enum App { - StartMenu, - Generating(GenProgress), - Loading, - Search(State), -} - -#[derive(Debug, Default)] -struct State { - input_value: String, - index : Index, - results : Vec -} - -struct SearchState { - search : String, - index : Index, -} - -#[derive(Debug, Clone, Default)] -struct GenProgress { - state: GenState, - progress: u8 -} - -impl SearchState { - pub async fn search(self) -> Vec { - let search_args = splitter::split_to_words(self.search); - self.index.search(search_args) - } -} - -#[derive(Clone, Debug)] -enum Message { - Load, - Generate, - Loaded(Index), - GeneratingUpdate(GenProgress), - InputChanged(String), - SearchSubmit, - SearchFinished(Vec), - ExportResults -} - -async fn load_file() -> Index { - let file = rfd::FileDialog::new().add_filter("index", &["idxs"]) - .set_directory(".") - .set_title("Choose Index File") - .pick_file(); - let file = file.unwrap(); - let file = file.to_str(); - let file = file.unwrap(); - Index::from_file(&file.to_string()) -} - -async fn generate() -> Index { - let file = rfd::FileDialog::new().add_filter("index", &["idxs"]) - .set_directory(".") - .set_title("Choose Index File") - .save_file(); - let file = file.unwrap(); - let file = file.to_str(); - let file = file.unwrap(); - - let input = rfd::FileDialog::new() - .set_directory(".") - .set_title("Choose Directory to Index") - .pick_folder(); - let input = input.unwrap(); - let input = input.to_str(); - let input = input.unwrap(); - let index = Index::generate(input, |t, p| { - *GENERATE_PROGRESS.lock().unwrap() = p; - *GENERATE_STATUS.lock().unwrap() = t; +use std::rc::Rc; + +use gtk::prelude::*; +use gtk::Application; + +use self::state::View; + +mod state; +mod welcome; +mod generate; + +pub fn run() { + let app = Application::builder() + .application_id("org.n8.indexsearch") + .build(); + + app.connect_activate(|app| { + let window = gtk::ApplicationWindow::builder() + .application(app) + .default_width(800) + .default_height(600) + .title("Index Search") + .build(); + + let mut vm = state::ViewManager::new(window); + let welcome = Rc::new(Mutex::new(welcome::Welcome::new())); + vm.add_view(welcome.clone()); + let generate = Rc::new(Mutex::new(generate::Generate::new())); + vm.add_view(generate.clone()); + + let vm = Rc::new(vm); + welcome.lock().unwrap().set_vm(Rc::clone(&vm)); + generate.lock().unwrap().set_vm(Rc::clone(&vm)); + vm.set_current_view("welcome"); + vm.get_window().show_all(); }); - index.save(file.to_string()); - index -} -async fn generate_update_timer() -> GenProgress { - thread::sleep(Duration::from_millis(100)); - let p; - let s; - { - p = *GENERATE_PROGRESS.lock().unwrap(); - s = *GENERATE_STATUS.lock().unwrap(); - } - GenProgress { state : s, progress: p } + app.run(); } - -impl Application for App { - type Message = Message; - type Theme = Theme; - type Executor = iced::executor::Default; - type Flags = (); - - fn new(_flags: ()) -> (App, Command) { - ( - App::StartMenu, - Command::none() - ) - } - - fn title(&self) -> String { - "Index Search".to_string() - } - - fn update(&mut self, message: Message) -> Command { - match self { - App::StartMenu => { - match message { - Message::Load => { - *self = App::Loading; - Command::batch(vec![ - Command::perform(load_file(), Message::Loaded) - ]) - } - Message::Generate => { - *self = App::Generating(Default::default()); - Command::batch(vec![ - Command::perform(generate(), Message::Loaded), - Command::perform(generate_update_timer(), Message::GeneratingUpdate) - ]) - } - _ => { - Command::none() - } - } - } - App::Loading => { - match message { - Message::Loaded(index) => { - *self = App::Search(State { index, ..Default::default() }); - Command::none() - } - _ => { - Command::none() - } - } - } - App::Generating(state) => { - match message { - Message::Loaded(index) => { - *self = App::Search(State { index, ..Default::default() }); - Command::none() - } - Message::GeneratingUpdate(s) => { - state.progress = s.progress; - state.state = s.state; - Command::perform(generate_update_timer(), Message::GeneratingUpdate) - } - _ => { - Command::none() - } - } - } - App::Search(state) => { - match message { - Message::InputChanged(value) => { - state.input_value = value; - Command::none() - } - Message::SearchSubmit => { - Command::perform( - SearchState { - search: state.input_value.clone(), - index : state.index.clone() - }.search(), - Message::SearchFinished - ) - } - Message::SearchFinished(results) => { - state.results = results; - Command::none() - } - Message::ExportResults => { - let file = rfd::FileDialog::new().set_title("Export to File").add_filter("Raw Text", &["txt"]).save_file(); - if let Some(file) = file { - let mut file = File::create(file).unwrap(); - for result in state.results.iter() { - writeln!(file, "{}", result.path).ok(); - } - } - Command::none() - } - _ => { - Command::none() - } - } - } - } - } - - fn view(&self) -> Element { - let container = container(match self { - App::StartMenu => { - column![ - button("Open").on_press(Message::Load), - button("Generate").on_press(Message::Generate), - ].spacing(10).align_items(iced::Alignment::Center) - } - App::Loading => { - column![ - text("Loading"), - circular::Circular::new() - ].spacing(10).align_items(iced::Alignment::Center) - } - App::Generating(state) => { - let t = match state.state { - GenState::Fetching => { text("Fetching") } - GenState::Parsing => { text("Parsing") } - GenState::Merging => { text("Merging") } - }; - column![ - t, - progress_bar(0.0..=100.0, state.progress as f32) - ].spacing(10).align_items(iced::Alignment::Center) - } - App::Search(State { input_value, index, results, ..}) => { - let res : Element = if results.is_empty() { - column![text("There are no results")].spacing(10).into() - } else { - let lines : Vec> = results.iter() - .map(|r| row![text(r.priority).width(Length::Fixed(50.0)), text(r.path.clone())].into()) - .collect(); - column(lines).spacing(10).into() - }; - - column![ - row![ - text_input("Search", input_value) - .id(SEARCH_ID.clone()) - .on_submit(Message::SearchSubmit) - .on_input(Message::InputChanged) - .padding(10), - text(format!("{} files", index.num_files())) - ].align_items(iced::Alignment::Center).spacing(10), - scrollable(res).width(Length::Fill).height(Length::Fill), - row![ - text(format!("{} results", results.len())).width(Length::Fill), - button("export results").on_press(Message::ExportResults) - ].align_items(iced::Alignment::End).spacing(10) - ].spacing(10) - } - }).center_x() - .padding(10) - .width(Length::Fill) - .height(Length::Fill); - - match self { - App::Search(_) => { - container.into() - } - _ => { - container.center_y().into() - } - } - } -} - diff --git a/src/gui/state.rs b/src/gui/state.rs new file mode 100644 index 0000000..7b9542a --- /dev/null +++ b/src/gui/state.rs @@ -0,0 +1,51 @@ +use gtk::prelude::*; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Mutex; + +pub trait View { + fn name(&self) -> &str; + fn set_vm(&mut self, vm : Rc); + fn make_current(&self) -> gtk::Box; +} + +#[derive(Default)] +pub struct ViewManager { + views : HashMap>>, + pub current : String, + pub window : Option +} + +impl ViewManager { + pub fn new(window : gtk::ApplicationWindow) -> Self { + Self { + views : HashMap::new(), + current : String::new(), + window : Some(window), + } + } + + pub fn empty() -> Self { + Self { + .. Default::default() + } + } + + pub fn add_view(&mut self, view : Rc>) { + self.views.insert(view.lock().unwrap().name().to_string(), view.clone()); + } + + pub fn set_current_view(&self, name : &str) { + let b = self.views.get(name).expect("unkown view").lock().unwrap().make_current(); + let window = self.window.as_ref().unwrap(); + for child in window.children() { + window.remove(&child) + } + window.add(&b); + window.show_all() + } + + pub fn get_window(&self) -> >k::ApplicationWindow { + self.window.as_ref().unwrap() + } +} diff --git a/src/gui/welcome.rs b/src/gui/welcome.rs new file mode 100644 index 0000000..3dac0dd --- /dev/null +++ b/src/gui/welcome.rs @@ -0,0 +1,44 @@ +use gtk::prelude::*; +use super::state::{View, ViewManager}; +use std::rc::Rc; + +pub struct Welcome { + vm : Rc +} + +impl View for Welcome { + fn name(&self) -> &str { + "welcome" + } + + fn set_vm(&mut self, vm : Rc) { + self.vm = vm + } + + fn make_current(&self) -> gtk::Box { + let center = gtk::Box::new(gtk::Orientation::Vertical, 10); + let main = gtk::Box::new(gtk::Orientation::Horizontal, 10); + let gen_button = gtk::Button::with_label("Generate"); + let merge_button = gtk::Button::with_label("Merge"); + let load_button = gtk::Button::with_label("Load"); + + main.pack_start(&gen_button, true, false, 10); + main.pack_start(&merge_button, true, false, 10); + main.pack_start(&load_button, true, false, 10); + + center.pack_start(&main, true, false, 0); + + let vm = Rc::clone(&self.vm); + gen_button.connect_clicked(move |_| { + vm.set_current_view("generate"); + }); + + center + } +} + +impl Welcome { + pub fn new() -> Self { + Self { vm : Rc::new(ViewManager::empty()) } + } +} -- cgit v1.2.3-70-g09d2