use iced::{
self, alignment, executor, keyboard, subscription,
widget::{column, container, image, row, text, text_input},
window, Alignment, Application, Command, ContentFit, Event, Length, Subscription,
};
use cpc;
use rand::{self, seq::SliceRandom};
use crate::{input, programs, theme, FONT_BOLD};
pub type Renderer = iced::Renderer<theme::GTheme>;
pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>;
pub type Column<'a, Message> = iced::widget::Column<'a, Message, Renderer>;
enum GandalfState {
Exec,
Calc,
}
pub struct Gandalf {
state: GandalfState,
image: image::Handle,
image_width: u16,
prompt: String,
prompt_input_id: text_input::Id,
output: String,
calc_output: Option<cpc::Number>,
programs: Vec<programs::Program>,
filtered_programs: Vec<programs::Program>,
/// cursor position
c_pos: usize,
/// cursor page
c_page: usize,
/// entries visible per page, entries per page
epp: usize,
}
#[derive(Debug, Clone)]
pub enum Message {
PromptChanged(String),
FocusPromptInput(Vec<programs::Program>),
MoveCursor(bool),
Execute,
Exit,
}
impl Application for Gandalf {
type Executor = executor::Default;
type Message = Message;
type Theme = theme::GTheme;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
// FIXME: why yes, i am indeed hard coding the images and their widths.
// i was not able to come up with a way to do it dynamically, but give me a break, its 1 am right now.
let images = vec![
(
image::Handle::from_memory(include_bytes!("../assets/001.jpg").as_slice()),
1200,
),
(
image::Handle::from_memory(include_bytes!("../assets/002.jpg").as_slice()),
479,
),
(
image::Handle::from_memory(include_bytes!("../assets/003.jpg").as_slice()),
471,
),
(
image::Handle::from_memory(include_bytes!("../assets/004.jpg").as_slice()),
510,
),
];
let (image_handle, image_width) = images.choose(&mut rand::thread_rng()).unwrap();
let gandalf = Gandalf {
state: GandalfState::Exec,
image: image_handle.clone(),
image_width: *image_width as u16,
prompt: String::from(""),
prompt_input_id: text_input::Id::unique(),
output: String::from(""),
calc_output: None,
programs: vec![],
filtered_programs: vec![],
c_pos: 0,
c_page: 0,
epp: 9,
};
(
gandalf,
Command::batch(vec![
window::set_mode(window::Mode::Fullscreen),
Command::perform(programs::fetch(), Message::FocusPromptInput),
]),
)
}
fn title(&self) -> String {
String::from("gandalf")
}
fn theme(&self) -> Self::Theme {
theme::GTheme::Light
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::PromptChanged(prompt) => {
self.prompt = prompt;
self.filter_programs();
// if no programs, go to calc
if self.filtered_programs.len() == 0 {
self.state = GandalfState::Calc;
let evald = cpc::eval(&self.prompt, true, cpc::units::Unit::Celsius, false);
match evald {
Ok(res) => {
self.calc_output = Some(res);
}
Err(res) => {
self.calc_output = None;
self.output = res.to_string();
}
}
} else {
self.state = GandalfState::Exec;
}
// TODO: other things to check
// if input starts with "set bl", show something like
// set backlight to N
// if input starts with "set vol", show something like
// set volume to N
Command::none()
}
Message::FocusPromptInput(programs) => {
self.programs = programs.clone();
self.filter_programs();
text_input::focus(self.prompt_input_id.clone())
}
Message::MoveCursor(d) => {
match d {
true => {
if self.c_pos > 0 {
if self.c_pos % self.epp == 0 {
self.c_page -= 1;
}
self.c_pos -= 1;
}
}
false => {
if self.c_pos < self.programs.len() - 1 {
if (self.c_pos + 1) % self.epp == 0 {
self.c_page += 1;
}
self.c_pos += 1;
}
}
}
Command::none()
}
Message::Execute => match self.state {
GandalfState::Exec => {
let program = self.filtered_programs.get(self.c_pos).unwrap();
match programs::launch(&program.path) {
Ok(_) => {
// Shit was successful
// TODO: I guess we could save history here for remembering popular choices.
window::close()
}
Err(_) => {
println!("got fucked while launching '{}'", program.path);
window::close()
}
}
}
GandalfState::Calc => match self.calc_output.clone() {
Some(output) => iced::clipboard::write(output.value.to_string()),
None => Command::none(),
},
},
Message::Exit => window::close(),
}
}
fn subscription(&self) -> Subscription<Self::Message> {
subscription::events_with(|event, _status| match event {
Event::Keyboard(keyboard::Event::KeyPressed {
modifiers: _,
key_code,
}) => input::handle(key_code),
//Event::Window(window::Event::Unfocused) => Some(Message::Exit),
_ => None,
})
}
fn view(&self) -> Element<'_, Self::Message> {
let info_col = column![
container(text("prompt:").width(Length::Shrink))
.width(Length::Units(self.image_width))
.align_x(alignment::Horizontal::Right),
image(self.image.clone()).content_fit(ContentFit::ScaleDown) //.height(Length::Units(300))
]
.padding(15);
let prompt_input = text_input("gandalf shall give", &self.prompt, Message::PromptChanged)
.id(self.prompt_input_id.clone())
.width(Length::Units(450));
let main_col_content: Column<Message>;
match self.state {
GandalfState::Exec => {
main_col_content = self
.filtered_programs
.iter()
.skip(self.c_page * self.epp)
.take(self.epp)
.enumerate()
.fold(Column::new(), |column, (i, program)| {
if (i + (self.c_page * self.epp)) == self.c_pos {
column.push(
text(program.exec.clone())
.style(theme::Text::Selected)
.font(FONT_BOLD),
)
} else {
column.push(text(program.exec.clone()))
}
})
.into();
}
GandalfState::Calc => {
main_col_content = column![text({
if self.calc_output.is_some() {
self.calc_output.clone().unwrap().value.to_string()
} else {
self.output.clone()
}
})];
}
}
let main_col = column![row![prompt_input], row![main_col_content]].padding(10);
let content = column![row![row![info_col, main_col,]]
.height(Length::Fill)
.align_items(Alignment::Center)]
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::Center);
content.into()
}
}
impl Gandalf {
fn filter_programs(&mut self) {
self.filtered_programs = self
.programs
.iter()
.filter(|program| program.exec.to_lowercase().contains(self.prompt.as_str()))
.cloned()
.collect();
self.c_pos = 0;
self.c_page = 0;
}
}