aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gui/circular.rs421
-rw-r--r--src/gui/easing.rs102
-rw-r--r--src/gui/generate.rs35
-rw-r--r--src/gui/mod.rs313
-rw-r--r--src/gui/state.rs51
-rw-r--r--src/gui/welcome.rs44
6 files changed, 159 insertions, 807 deletions
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: <Theme as StyleSheet>::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: <Theme as StyleSheet>::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: <Theme as StyleSheet>::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<Message, iced::Renderer<Theme>>
- for Circular<'a, Theme>
-where
- Message: 'a + Clone,
- Theme: StyleSheet,
-{
- fn tag(&self) -> tree::Tag {
- tree::Tag::of::<State>()
- }
-
- 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<Theme>,
- 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<Theme>,
- _clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- const FRAME_RATE: u64 = 60;
-
- let state = tree.state.downcast_mut::<State>();
-
- 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: &Theme,
- _style: &renderer::Style,
- layout: Layout<'_>,
- _cursor: mouse::Cursor,
- _viewport: &Rectangle,
- ) {
- let state = tree.state.downcast_ref::<State>();
- let bounds = layout.bounds();
- let custom_style =
- <Theme as StyleSheet>::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<Circular<'a, Theme>>
- for Element<'a, Message, iced::Renderer<Theme>>
-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<Background>,
- /// 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<Easing> = 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<BuilderImpl>);
-
-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<Point>) -> 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<Point>,
- to: impl Into<Point>,
- ) -> 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<Point>,
- ctrl2: impl Into<Point>,
- to: impl Into<Point>,
- ) -> 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<Point>) -> lyon_algorithms::geom::Point<f32> {
- 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<ViewManager>
+}
+
+impl View for Generate {
+ fn name(&self) -> &str {
+ "generate"
+ }
+
+ fn set_vm(&mut self, vm : Rc<ViewManager>) {
+ 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 std::rc::Rc;
-use crate::index::Index;
-use crate::searchresult::SearchResult;
-use crate::splitter;
-use crate::index::GenState;
+use gtk::prelude::*;
+use gtk::Application;
-mod easing;
-mod circular;
+use self::state::View;
-static SEARCH_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
-static GENERATE_PROGRESS : Lazy<Mutex<u8>> = Lazy::new(|| { Mutex::new(0) });
-static GENERATE_STATUS : Lazy<Mutex<GenState>> = Lazy::new(|| { Mutex::new(GenState::Fetching) });
+mod state;
+mod welcome;
+mod generate;
-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<SearchResult>
-}
-
-struct SearchState {
- search : String,
- index : Index,
-}
-
-#[derive(Debug, Clone, Default)]
-struct GenProgress {
- state: GenState,
- progress: u8
-}
-
-impl SearchState {
- pub async fn search(self) -> Vec<SearchResult> {
- 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<SearchResult>),
- ExportResults
-}
+pub fn run() {
+ let app = Application::builder()
+ .application_id("org.n8.indexsearch")
+ .build();
-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())
-}
+ app.connect_activate(|app| {
+ let window = gtk::ApplicationWindow::builder()
+ .application(app)
+ .default_width(800)
+ .default_height(600)
+ .title("Index Search")
+ .build();
-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 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 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;
+ 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 }
-}
-
-impl Application for App {
- type Message = Message;
- type Theme = Theme;
- type Executor = iced::executor::Default;
- type Flags = ();
- fn new(_flags: ()) -> (App, Command<Message>) {
- (
- App::StartMenu,
- Command::none()
- )
- }
-
- fn title(&self) -> String {
- "Index Search".to_string()
- }
-
- fn update(&mut self, message: Message) -> Command<Message> {
- 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<Message> {
- 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<Message> = if results.is_empty() {
- column![text("There are no results")].spacing(10).into()
- } else {
- let lines : Vec<Element<_>> = 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()
- }
- }
- }
+ app.run();
}
-
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<ViewManager>);
+ fn make_current(&self) -> gtk::Box;
+}
+
+#[derive(Default)]
+pub struct ViewManager {
+ views : HashMap<String, Rc<Mutex<dyn View>>>,
+ pub current : String,
+ pub window : Option<gtk::ApplicationWindow>
+}
+
+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<Mutex<dyn View>>) {
+ 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) -> &gtk::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<ViewManager>
+}
+
+impl View for Welcome {
+ fn name(&self) -> &str {
+ "welcome"
+ }
+
+ fn set_vm(&mut self, vm : Rc<ViewManager>) {
+ 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()) }
+ }
+}