Skip to content
94 changes: 88 additions & 6 deletions src/gui/focus_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ pub trait FocusData {

pub const FOCUS_WIDGET_SET_FOCUS_ON_HOVER: Selector<WidgetId> =
Selector::new("focus_widget.set_focus");
pub const FOCUS_WIDGET_RESIGN_FOCUS: Selector<WidgetId> =
Selector::new("focus_widget.resign_focus");

pub struct FocusWidget<S: druid::Data + FocusData, W> {
inner: W,
paint_fn_on_focus: fn(ctx: &mut PaintCtx, data: &S, env: &Env),
lifecycle_fn: fn(ctx: &mut LifeCycleCtx, data: &S, env: &Env),
hover_lost_fn: Option<fn(ctx: &mut LifeCycleCtx, data: &S, env: &Env)>,
env_fn_on_focus: Option<fn(&Env) -> Env>,
is_focused: bool,
}

impl<S: druid::Data + FocusData, W> FocusWidget<S, W> {}
Expand All @@ -29,8 +34,21 @@ impl<S: druid::Data + FocusData, W> FocusWidget<S, W> {
inner,
paint_fn_on_focus,
lifecycle_fn,
hover_lost_fn: None,
env_fn_on_focus: None,
is_focused: false,
}
}

pub fn on_hover_lost(mut self, f: fn(ctx: &mut LifeCycleCtx, data: &S, env: &Env)) -> Self {
self.hover_lost_fn = Some(f);
self
}

pub fn with_env_on_focus(mut self, f: fn(&Env) -> Env) -> Self {
self.env_fn_on_focus = Some(f);
self
}
}

impl<S: druid::Data + FocusData, W: Widget<S>> Widget<S> for FocusWidget<S, W> {
Expand All @@ -48,6 +66,14 @@ impl<S: druid::Data + FocusData, W: Widget<S>> Widget<S> for FocusWidget<S, W> {
ctx.set_handled();
ctx.request_update();
}
Event::Command(cmd) if cmd.is(FOCUS_WIDGET_RESIGN_FOCUS) => {
if ctx.has_focus() {
ctx.resign_focus();
}
ctx.request_paint();
ctx.set_handled();
ctx.request_update();
}
Event::WindowConnected => {
if data.has_autofocus() {
// ask for focus on launch
Expand Down Expand Up @@ -93,7 +119,17 @@ impl<S: druid::Data + FocusData, W: Widget<S>> Widget<S> for FocusWidget<S, W> {
_ => {}
}

self.inner.event(ctx, event, data, env);
let mut local_env = env.clone();
if ctx.has_focus() != self.is_focused {
self.is_focused = ctx.has_focus();
ctx.request_layout();
}
if self.is_focused {
if let Some(f) = self.env_fn_on_focus {
local_env = f(env);
}
}
self.inner.event(ctx, event, data, &local_env);
}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &S, env: &Env) {
Expand All @@ -104,15 +140,17 @@ impl<S: druid::Data + FocusData, W: Widget<S>> Widget<S> for FocusWidget<S, W> {
ctx.register_for_focus();
}
LifeCycle::FocusChanged(to_focused) => {
self.is_focused = *to_focused;
if *to_focused {
// enable scrolling once getting edge cases right
// (sometimes too eager to scroll top/bottom item)
if !ctx.is_hot() {
ctx.scroll_to_view();
}
(self.lifecycle_fn)(ctx, data, env);
}
(self.lifecycle_fn)(ctx, data, env);
ctx.request_paint();
ctx.request_layout();
}
LifeCycle::HotChanged(to_hot) => {
if *to_hot && !ctx.has_focus() {
Expand All @@ -125,27 +163,71 @@ impl<S: druid::Data + FocusData, W: Widget<S>> Widget<S> for FocusWidget<S, W> {
);
ctx.submit_command(cmd);
//ctx.request_paint();
} else if !*to_hot {
if let Some(f) = self.hover_lost_fn {
f(ctx, data, env);
}
if ctx.has_focus() {
let cmd = Command::new(
FOCUS_WIDGET_RESIGN_FOCUS,
ctx.widget_id(),
Target::Widget(ctx.widget_id()),
);
ctx.submit_command(cmd);
}
}
}
_ => {}
}
self.inner.lifecycle(ctx, event, data, env);
let mut local_env = env.clone();
if ctx.has_focus() != self.is_focused {
self.is_focused = ctx.has_focus();
ctx.request_layout();
}
if self.is_focused {
if let Some(f) = self.env_fn_on_focus {
local_env = f(env);
}
}
self.inner.lifecycle(ctx, event, data, &local_env);
}

fn update(&mut self, ctx: &mut UpdateCtx, old_data: &S, data: &S, env: &Env) {
/*if old_data.glow_hot != data.glow_hot {
ctx.request_paint();
}*/
self.inner.update(ctx, old_data, data, env);
let mut local_env = env.clone();
if ctx.has_focus() != self.is_focused {
self.is_focused = ctx.has_focus();
ctx.request_layout();
}
if self.is_focused {
if let Some(f) = self.env_fn_on_focus {
local_env = f(env);
}
}
self.inner.update(ctx, old_data, data, &local_env);
}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &S, env: &Env) -> Size {
self.inner.layout(ctx, bc, data, env)
let mut local_env = env.clone();
// info!("FocusWidget: layout, widget_id: {:?}, is_focused: {}", ctx.widget_id(), self.is_focused);
if self.is_focused {
if let Some(f) = self.env_fn_on_focus {
local_env = f(env);
}
}
self.inner.layout(ctx, bc, data, &local_env)
}

fn paint(&mut self, ctx: &mut PaintCtx, data: &S, env: &Env) {
if ctx.has_focus() {
if self.is_focused {
(self.paint_fn_on_focus)(ctx, data, env);
if let Some(f) = self.env_fn_on_focus {
let new_env = f(env);
self.inner.paint(ctx, data, &new_env);
return;
}
}
self.inner.paint(ctx, data, env);
}
Expand Down
116 changes: 76 additions & 40 deletions src/gui/main_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ impl MainWindow {
const BOTTOM_ROW_HEIGHT: f64 = 18.0;

let url_label = Label::dynamic(|data: &UIState, _| ellipsize(data.url.as_str(), 28))
.with_text_size(12.0)
.with_text_color(Color::from_hex_str("808080").unwrap())
.with_font(MainWindowTheme::ENV_PROFILE_LABEL_FONT)
.with_text_color(MainWindowTheme::ENV_PROFILE_LABEL_COLOR)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.fix_height(BOTTOM_ROW_HEIGHT)
Expand Down Expand Up @@ -322,22 +322,24 @@ pub(crate) fn calculate_window_position(
return Point::new(x, y);
}

fn create_browser_label() -> Label<((bool, UISettings), UIBrowser)> {
let browser_label = Label::dynamic(
|((incognito_mode, _), item): &((bool, UISettings), UIBrowser), _env| {
let mut name = item.browser_name.clone();
if item.supports_incognito && *incognito_mode {
name += " 👓";
}
name
},
)
.with_text_size(MainWindowTheme::ENV_BROWSER_LABEL_SIZE)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.with_text_color(MainWindowTheme::ENV_BROWSER_LABEL_COLOR);
fn create_browser_label() -> impl Widget<((bool, UISettings), UIBrowser)> {
let label_fn = |((_incognito_mode, _), item): &((bool, UISettings), UIBrowser), _env: &_| {
item.browser_name.clone()
};

browser_label
Either::new(
|(_, item): &((bool, UISettings), UIBrowser), _| item.is_focused,
Label::dynamic(label_fn)
.with_font(MainWindowTheme::ENV_BROWSER_LABEL_FONT)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.with_text_color(MainWindowTheme::ENV_HOVER_TEXT_COLOR),
Label::dynamic(label_fn)
.with_font(MainWindowTheme::ENV_BROWSER_LABEL_FONT)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.with_text_color(MainWindowTheme::ENV_BROWSER_LABEL_COLOR),
)
}

fn create_browser(
Expand Down Expand Up @@ -372,14 +374,23 @@ fn create_browser(
let item_label = Either::new(
|(_, item): &((bool, UISettings), UIBrowser), _env| item.supports_profiles,
{
let profile_label =
Label::dynamic(|(_, item): &((bool, UISettings), UIBrowser), _env: &_| {
item.profile_name.clone()
})
.with_text_size(MainWindowTheme::ENV_PROFILE_LABEL_SIZE)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.with_text_color(MainWindowTheme::ENV_PROFILE_LABEL_COLOR);
let profile_label_fn = |(_, item): &((bool, UISettings), UIBrowser), _env: &_| {
item.profile_name.clone()
};

let profile_label = Either::new(
|(_, item): &((bool, UISettings), UIBrowser), _| item.is_focused,
Label::dynamic(profile_label_fn)
.with_font(MainWindowTheme::ENV_PROFILE_LABEL_FONT)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.with_text_color(MainWindowTheme::ENV_HOVER_SECONDARY_TEXT_COLOR),
Label::dynamic(profile_label_fn)
.with_font(MainWindowTheme::ENV_PROFILE_LABEL_FONT)
.with_line_break_mode(LineBreaking::Clip)
.with_text_alignment(TextAlignment::Start)
.with_text_color(MainWindowTheme::ENV_PROFILE_LABEL_COLOR),
);

let profile_row = Flex::row()
//.with_child(profile_icon)
Expand Down Expand Up @@ -415,21 +426,30 @@ fn create_browser(
ui_settings.visual_settings.show_hotkeys && item.filtered_index < 9
},
{
let hotkey_label =
let make_hotkey_label = |color: druid::Key<Color>| {
Label::dynamic(|(_, item): &((bool, UISettings), UIBrowser), _env: &_| {
let hotkey_number = item.filtered_index + 1;
let hotkey = hotkey_number.to_string();
hotkey
})
.with_font(font)
.with_text_color(MainWindowTheme::ENV_HOTKEY_TEXT_COLOR)
.with_font(font.clone())
.with_text_color(color)
.fix_size(text_size, text_size)
.padding(4.0);

let hotkey_label = Container::new(hotkey_label)
.background(MainWindowTheme::ENV_HOTKEY_BACKGROUND_COLOR)
.rounded(5.0)
.border(MainWindowTheme::ENV_HOTKEY_BORDER_COLOR, 0.5);
};

let hotkey_label = Either::new(
|(_, item): &((bool, UISettings), UIBrowser), _| item.is_focused,
make_hotkey_label(MainWindowTheme::ENV_HOVER_HOTKEY_TEXT_COLOR)
.padding(4.0)
.background(MainWindowTheme::ENV_HOVER_HOTKEY_BACKGROUND_COLOR)
.rounded(5.0)
.border(MainWindowTheme::ENV_HOTKEY_BORDER_COLOR, 0.5),
make_hotkey_label(MainWindowTheme::ENV_HOTKEY_TEXT_COLOR)
.padding(4.0)
.background(MainWindowTheme::ENV_HOTKEY_BACKGROUND_COLOR)
.rounded(5.0)
.border(MainWindowTheme::ENV_HOTKEY_BORDER_COLOR, 0.5),
);

hotkey_label
},
Expand Down Expand Up @@ -459,11 +479,13 @@ fn create_browser(

let container = FocusWidget::new(
container,
|ctx, _: &((bool, UISettings), UIBrowser), _env| {
let size = ctx.size();
let rounded_rect = size.to_rounded_rect(5.0);
let color = Color::rgba(1.0, 1.0, 1.0, 0.25);
ctx.fill(rounded_rect, &color);
|ctx, (_, data): &((bool, UISettings), UIBrowser), env| {
if data.is_focused {
let size = ctx.size();
let rounded_rect = size.to_rounded_rect(5.0);
let color = env.get(MainWindowTheme::ENV_HOVER_BACKGROUND_COLOR);
ctx.fill(rounded_rect, &color);
}
},
|ctx, (_, data): &((bool, UISettings), UIBrowser), _env| {
if ctx.has_focus() {
Expand All @@ -476,7 +498,21 @@ fn create_browser(
.ok();
}
},
);
)
.on_hover_lost(|ctx, (_, data), _| {
if data.is_focused {
ctx.get_external_handle()
.submit_command(
SET_FOCUSED_INDEX,
None,
Target::Global,
)
.ok();
}
})
.env_scope(|env, (_, data): &((bool, UISettings), UIBrowser)| {
// env scope removed as we handle colors explicitly
});

let container = Container::new(container);

Expand Down
Loading