Raw File
status_bar.rs
/*
ENSnano, a 3d graphical application for DNA nanostructures.
    Copyright (C) 2021  Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>

    This program 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.

    This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
*/
use super::Requests;
use crate::mediator::{Operation, ParameterField, Selection};
use iced::{container, slider, Background, Container, Length};
use iced_native::{pick_list, text_input, Color, PickList, TextInput};
use iced_winit::{Column, Command, Element, Program, Row, Space, Text};
use std::sync::{Arc, Mutex};

const STATUS_FONT_SIZE: u16 = 14;

#[derive(Debug)]
enum StatusParameter {
    Value(text_input::State),
    Choice(pick_list::State<String>),
}

impl StatusParameter {
    fn get_value(&mut self) -> &mut text_input::State {
        match self {
            StatusParameter::Value(ref mut state) => state,
            _ => panic!("wrong status parameter variant"),
        }
    }

    fn get_choice(&mut self) -> &mut pick_list::State<String> {
        match self {
            StatusParameter::Choice(ref mut state) => state,
            _ => panic!("wrong status parameter variant"),
        }
    }

    fn value() -> Self {
        Self::Value(Default::default())
    }

    fn choice() -> Self {
        Self::Choice(Default::default())
    }

    fn has_keyboard_priority(&self) -> bool {
        match self {
            Self::Choice(_) => false,
            Self::Value(state) => state.is_focused(),
        }
    }
}

pub struct StatusBar {
    parameters: Vec<StatusParameter>,
    info_values: Vec<String>,
    operation_values: Vec<String>,
    operation: Option<Arc<dyn Operation>>,
    requests: Arc<Mutex<Requests>>,
    selection: Selection,
    progress: Option<(String, f32)>,
    #[allow(dead_code)]
    slider_state: slider::State,
}

impl StatusBar {
    pub fn new(requests: Arc<Mutex<Requests>>) -> Self {
        Self {
            parameters: Vec::new(),
            info_values: Vec::new(),
            operation_values: Vec::new(),
            operation: None,
            requests,
            selection: Selection::Nothing,
            progress: None,
            slider_state: Default::default(),
        }
    }

    pub fn update_op(&mut self, operation: Arc<dyn Operation>) {
        let parameters = operation.parameters();
        let mut new_param = Vec::new();
        for p in parameters.iter() {
            match p.field {
                ParameterField::Choice(_) => new_param.push(StatusParameter::choice()),
                ParameterField::Value => new_param.push(StatusParameter::value()),
            }
        }
        self.operation_values = operation.values().clone();
        self.parameters = new_param;
    }

    fn view_op(&mut self) -> Element<Message, iced_wgpu::Renderer> {
        let mut row = Row::new();
        let op = self.operation.as_ref().unwrap(); // the function view op is only called when op is some.
        row = row.push(Text::new(op.description()).size(STATUS_FONT_SIZE));
        let values = &self.operation_values;
        for (i, p) in self.parameters.iter_mut().enumerate() {
            let param = &op.parameters()[i];
            match param.field {
                ParameterField::Value => {
                    row = row
                        .spacing(20)
                        .push(Text::new(param.name.clone()).size(STATUS_FONT_SIZE))
                        .push(
                            TextInput::new(
                                p.get_value(),
                                "",
                                &format!("{0:.4}", values[i]),
                                move |s| Message::ValueChanged(i, s),
                            )
                            .size(STATUS_FONT_SIZE)
                            .width(Length::Units(40)),
                        )
                }
                ParameterField::Choice(ref v) => {
                    row = row.spacing(20).push(
                        PickList::new(
                            p.get_choice(),
                            v.clone(),
                            Some(values[i].clone()),
                            move |s| Message::ValueChanged(i, s),
                        )
                        .text_size(STATUS_FONT_SIZE - 4),
                    )
                }
            }
        }
        row.into()
    }

    fn view_selection(&mut self) -> Element<Message, iced_wgpu::Renderer> {
        let mut row = Row::new();
        if self.selection != Selection::Nothing {
            row = row.push(Text::new(self.selection.info()).size(STATUS_FONT_SIZE));
        }
        row.into()
    }

    fn view_progress(&mut self) -> Element<Message, iced_wgpu::Renderer> {
        let mut row = Row::new();
        let progress = self.progress.as_ref().unwrap();
        row = row.push(
            Text::new(format!("{}, {:.1}%", progress.0, progress.1 * 100.)).size(STATUS_FONT_SIZE),
        );

        row.into()
    }

    pub fn has_keyboard_priority(&self) -> bool {
        self.parameters.iter().any(|p| p.has_keyboard_priority())
    }
}

#[derive(Clone, Debug)]
pub enum Message {
    Operation(Arc<dyn Operation>),
    Selection(Selection, Vec<String>),
    ValueChanged(usize, String),
    Progress(Option<(String, f32)>),
    #[allow(dead_code)]
    SetShift(f32),
    ClearOp,
}

impl Program for StatusBar {
    type Message = Message;
    type Renderer = iced_wgpu::Renderer;
    type Clipboard = iced_native::clipboard::Null;

    fn update(
        &mut self,
        message: Message,
        _cb: &mut iced_native::clipboard::Null,
    ) -> Command<Message> {
        match message {
            Message::Operation(ref op) => {
                self.operation = Some(op.clone());
                self.update_op(op.clone());
            }
            Message::ValueChanged(n, s) => {
                self.operation_values[n] = s.clone();
                let new_op = self
                    .operation
                    .as_ref()
                    .and_then(|op| op.with_new_value(n, s));
                if let Some(ref op) = new_op {
                    self.operation = Some(op.clone());
                }
                self.requests.lock().unwrap().operation_update = new_op;
            }
            Message::Progress(progress) => self.progress = progress,
            Message::Selection(s, v) => {
                self.operation = None;
                self.selection = s;
                self.info_values = v;
            }
            Message::ClearOp => self.operation = None,
            Message::SetShift(f) => {
                self.info_values[2] = f.to_string();
                self.requests.lock().unwrap().new_shift_hyperboloid = Some(f);
            }
        }
        Command::none()
    }

    fn view(&mut self) -> Element<Message, iced_wgpu::Renderer> {
        let content = if self.progress.is_some() {
            self.view_progress()
        } else if self.operation.is_some() {
            self.view_op()
        } else {
            self.view_selection()
        };

        let column = Column::new()
            .push(Space::new(Length::Fill, Length::Units(3)))
            .push(content);
        Container::new(column)
            .style(StatusBarStyle)
            .width(Length::Fill)
            .height(Length::Fill)
            .into()
    }
}

struct StatusBarStyle;
impl container::StyleSheet for StatusBarStyle {
    fn style(&self) -> container::Style {
        container::Style {
            background: Some(Background::Color(BACKGROUND)),
            text_color: Some(Color::WHITE),
            ..container::Style::default()
        }
    }
}

pub const BACKGROUND: Color = Color::from_rgb(
    0x12 as f32 / 255.0,
    0x12 as f32 / 255.0,
    0x30 as f32 / 255.0,
);
back to top