Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve display of errors in Jolly #22

Merged
merged 1 commit into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl StoreEntry {
(None, None, None) => name.to_string(),
_ => {
return Err(Error::CustomError(format!(
"Error with {}: Only allow one of location/url/system",
"Error with entry ['{}']: The entry should only specify one of location/url/system keys",
&name
)))
}
Expand Down
22 changes: 13 additions & 9 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@ pub enum Error {

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Final message is used when we want to say something, but it
// isn't an error, so we don't say error
if !matches!(self, Error::FinalMessage(_)) {
write!(f, "Error: ")?;
}

match self {
Error::StoreError(e) => e.fmt(f),
Error::StoreError(e) => {
write!(f, "while parsing jolly.toml: \n")?;
e.fmt(f)
}
Error::IcedError(e) => e.fmt(f),
Error::IoError(e) => e.fmt(f),
Error::ParseError(e) => e.fmt(f),
Error::IoError(e) => {
write!(f, "could not access jolly.toml: \n")?;
e.fmt(f)
}
Error::ParseError(e) => {
write!(f, "while parsing jolly.toml: \n")?;
e.fmt(f)
}
Error::PlatformError(e) => e.fmt(f),
Error::CustomError(s) => f.write_str(s),
// not really an error, used to represent final message in UI
Error::FinalMessage(s) => f.write_str(s),
}
}
Expand Down
125 changes: 76 additions & 49 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use iced::widget::TextInput;
use iced::widget::{Text, TextInput};
use iced::{executor, Application, Command, Element, Renderer};
use iced_native::widget::text_input;
use iced_native::{clipboard, command, event, keyboard, subscription, widget, window};
Expand All @@ -34,14 +34,14 @@ pub enum Message {
SearchTextChanged(String),
ExternalEvent(event::Event),
EntrySelected(entry::EntryId),
HeightChanged(u32),
DimensionsChanged(f32, f32),
StartedIconWorker(mpsc::Sender<icon::IconCommand>),
IconReceived(icon::IconType, icon::Icon),
}

enum StoreLoadedState {
Pending,
LoadFailed(String),
Finished(error::Error),
LoadSucceeded(store::Store, String),
}

Expand All @@ -59,17 +59,13 @@ pub struct Jolly {
modifiers: keyboard::Modifiers,
settings: settings::Settings,
icache: icon::IconCache,
bounds: iced_native::Rectangle,
}

impl Jolly {
fn move_to_err(&mut self, err: error::Error) -> Command<<Jolly as Application>::Message> {
self.store_state = StoreLoadedState::LoadFailed(err.to_string());
self.searchtext = String::new();
self.search_results = Default::default();
Command::single(command::Action::Window(window::Action::Resize {
width: self.settings.ui.width as _,
height: self.settings.ui.search.starting_height(),
}))
self.store_state = StoreLoadedState::Finished(err);
Command::none()
}

fn min_height_command(&self) -> Command<<Jolly as Application>::Message> {
Expand Down Expand Up @@ -111,24 +107,26 @@ impl Jolly {
}

impl Application for Jolly {
type Message = Message;
type Executor = executor::Default;
type Flags = config::Config;
type Message = Message;
type Theme = theme::Theme;
type Flags = config::Config;

fn new(config: Self::Flags) -> (Self, Command<Self::Message>) {
let mut jolly = Self::default();

jolly.settings = config.settings;

jolly.bounds.width = jolly.settings.ui.width as f32;

jolly.store_state = match config.store {
Ok(store) => {
let msg = format!("Loaded {} entries", store.len());
StoreLoadedState::LoadSucceeded(store, msg)
}
Err(e) => {
println!("{:?}", e);
StoreLoadedState::LoadFailed(e.to_string().replace("\n", " "))
eprintln!("{e}");
StoreLoadedState::Finished(e)
}
};

Expand Down Expand Up @@ -166,6 +164,23 @@ impl Application for Jolly {
Message::ExternalEvent(event::Event::Window(w)) if w == window::Event::Unfocused => {
return iced::window::close();
}

// handle height change even if UI has failed to load
Message::DimensionsChanged(width, height) => {
let width = if matches!(self.store_state, StoreLoadedState::Finished(_)) {
width
} else {
self.settings.ui.width as _
};

self.bounds.width = width;
self.bounds.height = height;

return Command::single(command::Action::Window(window::Action::Resize {
width: width.ceil() as u32,
height: height.ceil() as u32,
}));
}
_ => (), // dont care at this point about other messages
};

Expand All @@ -177,12 +192,6 @@ impl Application for Jolly {

// if we are here, we are loaded and we dont want to quit
match message {
Message::HeightChanged(height) => {
Command::single(command::Action::Window(window::Action::Resize {
width: self.settings.ui.width as _,
height: height,
}))
}
Message::SearchTextChanged(txt) => {
self.searchtext = txt;

Expand Down Expand Up @@ -258,44 +267,62 @@ impl Application for Jolly {
}
}

fn subscription(&self) -> iced::Subscription<Message> {
let channel = subscription::run(icon::icon_worker);
let external = subscription::events().map(Message::ExternalEvent);
subscription::Subscription::batch([channel, external].into_iter())
}

fn view(&self) -> Element<'_, Message, Renderer<Self::Theme>> {
use StoreLoadedState::*;
let default_txt = match &self.store_state {
Pending => "Loading Bookmarks... ",
LoadFailed(msg) => msg,
LoadSucceeded(_, msg) => msg,
};

let mut column = widget::column::Column::new();
column = column.push(
TextInput::new(default_txt, &self.searchtext)
.on_input(Message::SearchTextChanged)
.size(self.settings.ui.search.common.text_size())
.id(TEXT_INPUT_ID.clone())
.padding(self.settings.ui.search.padding),
);
let ui: Element<_, Renderer<Self::Theme>> = match &self.store_state {
LoadSucceeded(store, msg) => widget::column::Column::new()
.push(
TextInput::new(msg, &self.searchtext)
.on_input(Message::SearchTextChanged)
.size(self.settings.ui.search.common.text_size())
.id(TEXT_INPUT_ID.clone())
.padding(self.settings.ui.search.padding),
)
.push(
self.search_results
.view(&self.searchtext, store, Message::EntrySelected),
)
.into(),
Pending => Text::new("Loading Bookmarks...").into(),
Finished(err) => {
let errtext = Text::new(err.to_string());
let style;
let children;
if let error::Error::FinalMessage(_) = err {
style = theme::ContainerStyle::Transparent;
children = vec![errtext.into()];
} else {
style = theme::ContainerStyle::Error;
let title = Text::new("Oops, Jolly has encountered an Error...")
.style(iced::theme::Text::Color(
ui::Color::from_str("#D64541").into(),
))
.size(2 * self.settings.ui.search.common.text_size());
children = vec![title.into(), errtext.into()];
}

let col = widget::column::Column::with_children(children).spacing(5);

// we can only continue if the store is loaded
let store = match &self.store_state {
StoreLoadedState::LoadSucceeded(s, _) => s,
_ => return column.into(),
iced::widget::container::Container::new(col)
.style(style)
.padding(5)
.width(iced_native::Length::Fill)
.into()
}
};

column = column.push(self.search_results.view(
&self.searchtext,
store,
Message::EntrySelected,
));
measured_container::MeasuredContainer::new(column, Message::HeightChanged).into()
measured_container::MeasuredContainer::new(ui, Message::DimensionsChanged, self.bounds)
.into()
}

fn theme(&self) -> Self::Theme {
self.settings.ui.theme.clone()
}

fn subscription(&self) -> iced::Subscription<Message> {
let channel = subscription::run(icon::icon_worker);
let external = subscription::events().map(Message::ExternalEvent);
subscription::Subscription::batch([channel, external].into_iter())
}
}
30 changes: 20 additions & 10 deletions src/measured_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,27 @@ use iced_native::{Clipboard, Element, Layout, Length, Point, Rectangle, Shell, W

pub struct MeasuredContainer<'a, Message, Renderer, F>
where
F: 'static + Copy + Fn(u32) -> Message,
F: 'static + Copy + Fn(f32, f32) -> Message,
{
content: Element<'a, Message, Renderer>,
msg_builder: F,
old_bounds: iced::Rectangle,
}

impl<'a, Message, Renderer, F> MeasuredContainer<'a, Message, Renderer, F>
where
F: 'static + Copy + Fn(u32) -> Message,
F: 'static + Copy + Fn(f32, f32) -> Message,
{
/// Creates a [`MeasuredContainer`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, Renderer>>, callback: F) -> Self {
pub fn new(
content: impl Into<Element<'a, Message, Renderer>>,
callback: F,
old_bounds: Rectangle,
) -> Self {
MeasuredContainer {
content: content.into(),
msg_builder: callback,
old_bounds: old_bounds,
}
}
}
Expand All @@ -38,7 +44,7 @@ impl<'a, Message, Renderer, F> Widget<Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
F: 'static + Copy + Fn(u32) -> Message,
F: 'static + Copy + Fn(f32, f32) -> Message,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
Expand Down Expand Up @@ -92,16 +98,20 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let orig_width = layout.bounds().width;
let orig_height = layout.bounds().height.ceil() as u32;
let (orig_width, orig_height) = (self.old_bounds.width, self.old_bounds.height);

let limits = iced_native::layout::Limits::new(
iced_native::Size::ZERO,
iced_native::Size::new(orig_width, f32::INFINITY),
);

let new_height = self.layout(renderer, &limits).bounds().height.ceil() as u32;
if new_height != orig_height {
shell.publish((self.msg_builder)(new_height));
let bounds = self.layout(renderer, &limits).bounds();
let new_width = bounds.width;
let new_height = bounds.height;

if new_height != orig_height || new_width != orig_width {
shell.publish((self.msg_builder)(new_width, new_height));
self.old_bounds = bounds;
}

if let event::Status::Captured = self.content.as_widget_mut().on_event(
Expand Down Expand Up @@ -173,7 +183,7 @@ impl<'a, Message, Renderer, F> From<MeasuredContainer<'a, Message, Renderer, F>>
where
Message: 'a + Clone,
Renderer: 'a + iced_native::Renderer,
F: 'static + Copy + Fn(u32) -> Message,
F: 'static + Copy + Fn(f32, f32) -> Message,
{
fn from(area: MeasuredContainer<'a, Message, Renderer, F>) -> Element<'a, Message, Renderer> {
Element::new(area)
Expand Down
12 changes: 12 additions & 0 deletions src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ pub enum ContainerStyle {
#[default]
Transparent,
Selected,
Error,
}

impl container::StyleSheet for Theme {
Expand All @@ -306,6 +307,17 @@ impl container::StyleSheet for Theme {
border_color: iced_native::Color::TRANSPARENT,
}
}

ContainerStyle::Error => {
let bg_color: iced::Color = self.background_color.clone().into();
container::Appearance {
text_color: Some(self.text_color.clone().into()),
background: Some(bg_color.into()),
border_radius: 5.0,
border_width: 2.0,
border_color: ui::Color::from_str("#D64541").into(),
}
}
}
}
}
Expand Down