Skip to content

Commit

Permalink
Improved display of errors in Jolly (#22)
Browse files Browse the repository at this point in the history
Error messages are not longer cut off if they are longer than one line.
  • Loading branch information
apgoetz committed Jul 29, 2023
1 parent 5e41e4a commit 352a253
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 69 deletions.
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

0 comments on commit 352a253

Please sign in to comment.