popup fixes and clock gets nicer popup
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# Link with mold from the repo itself, so `cargo build`/`cargo run` do not
|
||||||
|
# depend on a machine-local ~/.cargo/config.toml. -fuse-ld is understood by
|
||||||
|
# both the clang and gcc drivers, so this works with whatever `linker` the
|
||||||
|
# caller has configured (mold is in the devShell PATH; the nix build gets it
|
||||||
|
# via flake.nix).
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
@@ -2,16 +2,24 @@ use iced::{window, Rectangle};
|
|||||||
|
|
||||||
use crate::Wire;
|
use crate::Wire;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PopupSettings {
|
||||||
|
// pub name: String,
|
||||||
|
pub module_id: String,
|
||||||
|
pub element_id: String,
|
||||||
|
pub gap: i32,
|
||||||
|
}
|
||||||
|
|
||||||
/// What a module asks the app to do — the single effect vocabulary shared by
|
/// What a module asks the app to do — the single effect vocabulary shared by
|
||||||
/// modules and app, so mapping module output into a `Message` is a plain
|
/// modules and app, so mapping module output into a `Message` is a plain
|
||||||
/// `.map(Message::Effect)` with no mirror enum.
|
/// `.map(Message::Effect)` with no mirror enum.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ModuleEffect {
|
pub enum ModuleEffect {
|
||||||
/// Toggle a popup for the given module id (element id anchors it).
|
/// Toggle a popup for the given module id (element id anchors it).
|
||||||
RequestPopup(String, String),
|
RequestPopup(PopupSettings),
|
||||||
/// A widget-tree pass reported a module's laid-out bounds; anchor the
|
/// A widget-tree pass reported a module's laid-out bounds; anchor the
|
||||||
/// popup there. App-internal, but one effect type keeps routing trivial.
|
/// popup there. App-internal, but one effect type keeps routing trivial.
|
||||||
BoundsFound(String, Rectangle),
|
BoundsFound(Rectangle, PopupSettings),
|
||||||
/// Request removal of the popup surface.
|
/// Request removal of the popup surface.
|
||||||
ClosePopup(window::Id),
|
ClosePopup(window::Id),
|
||||||
/// Route a wire to its target (a module, a service inbox, or a topic).
|
/// Route a wire to its target (a module, a service inbox, or a topic).
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ mod wire;
|
|||||||
|
|
||||||
pub use config::{BarbarConfig, Modules};
|
pub use config::{BarbarConfig, Modules};
|
||||||
pub use display::Display;
|
pub use display::Display;
|
||||||
pub use effect::ModuleEffect;
|
pub use effect::{ModuleEffect, PopupSettings};
|
||||||
pub use messages::*;
|
pub use messages::*;
|
||||||
pub use module::BarModule;
|
pub use module::BarModule;
|
||||||
pub use reusable_elements::pill;
|
pub use reusable_elements::pill;
|
||||||
|
|||||||
+44
-25
@@ -5,12 +5,12 @@
|
|||||||
//! then opens the popup there; the active module renders its content.
|
//! then opens the popup there; the active module renders its content.
|
||||||
|
|
||||||
use iced::advanced::widget as advanced_widget;
|
use iced::advanced::widget as advanced_widget;
|
||||||
use iced::widget::{column, mouse_area, text};
|
use iced::widget::{column, container, mouse_area, text};
|
||||||
use iced::{Alignment, Element, Length, Rectangle, Task};
|
use iced::{Alignment, Border, Element, Length, Padding, Rectangle, Task};
|
||||||
use iced_layershell::actions::IcedNewPopupSettings;
|
use iced_layershell::actions::IcedNewPopupSettings;
|
||||||
use iced_layershell::reexport::{PopupAnchor, PopupGravity};
|
use iced_layershell::reexport::{PopupAnchor, PopupGravity};
|
||||||
|
|
||||||
use common::ModuleEffect;
|
use common::{pill, ModuleEffect, PopupSettings};
|
||||||
|
|
||||||
use crate::msg::popup_open;
|
use crate::msg::popup_open;
|
||||||
use crate::{Bar, Message, Msg};
|
use crate::{Bar, Message, Msg};
|
||||||
@@ -19,17 +19,17 @@ use crate::{Bar, Message, Msg};
|
|||||||
const POPUP_W: u32 = 150;
|
const POPUP_W: u32 = 150;
|
||||||
const POPUP_H: u32 = 100;
|
const POPUP_H: u32 = 100;
|
||||||
|
|
||||||
/// Gap (logical px) between the bar's bottom edge and the popup.
|
// Gap (logical px) between the bar's bottom edge and the popup.
|
||||||
const POPUP_GAP: i32 = 32;
|
// const POPUP_GAP: i32 = 32;
|
||||||
|
|
||||||
/// Opens a popup for `module_id` below its laid-out `bounds`. Size comes
|
/// Opens a popup for `module_id` below its laid-out `bounds`. Size comes
|
||||||
/// from the module's `popup_size()` (default: small menu).
|
/// from the module's `popup_size()` (default: small menu).
|
||||||
pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task<Message> {
|
pub fn open_popup(bar: &mut Bar, bounds: Rectangle, settings: PopupSettings) -> Task<Message> {
|
||||||
bar.active_popup = Some(module_id.clone());
|
bar.active_popup = Some(settings.module_id.clone());
|
||||||
|
|
||||||
let (w, h) = bar
|
let (w, h) = bar
|
||||||
.modules
|
.modules
|
||||||
.get(module_id.as_str())
|
.get(settings.module_id.as_str())
|
||||||
.and_then(|m| m.popup_size())
|
.and_then(|m| m.popup_size())
|
||||||
.unwrap_or((POPUP_W, POPUP_H));
|
.unwrap_or((POPUP_W, POPUP_H));
|
||||||
|
|
||||||
@@ -41,14 +41,14 @@ pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task<M
|
|||||||
bounds.width.round() as i32,
|
bounds.width.round() as i32,
|
||||||
bounds.height.round() as i32,
|
bounds.height.round() as i32,
|
||||||
);
|
);
|
||||||
let anchor_rect = (bx, by, bw, bh + POPUP_GAP);
|
let anchor_rect = (bx, by, bw, bh + settings.gap);
|
||||||
let settings = IcedNewPopupSettings::on_current_surface((w, h), anchor_rect)
|
let settings_popup = IcedNewPopupSettings::on_current_surface((w, h), anchor_rect)
|
||||||
.anchor(PopupAnchor::Bottom)
|
.anchor(PopupAnchor::Bottom)
|
||||||
.gravity(PopupGravity::Bottom);
|
.gravity(PopupGravity::Bottom);
|
||||||
|
|
||||||
let (id, task) = popup_open(settings);
|
let (id, task) = popup_open(settings_popup);
|
||||||
bar.popup_id = Some(id);
|
bar.popup_id = Some(id);
|
||||||
tracing::debug!(module = %module_id, ?id, size = ?(w, h), "popup opened");
|
tracing::debug!(module = %settings.module_id, ?id, size = ?(w, h), "popup opened");
|
||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ pub fn close_popup(bar: &mut Bar) -> Task<Message> {
|
|||||||
|
|
||||||
/// Runs a widget-tree [`Operation`] that captures the laid-out bounds of the
|
/// Runs a widget-tree [`Operation`] that captures the laid-out bounds of the
|
||||||
/// given element, then reports them via `BoundsFound` for the module.
|
/// given element, then reports them via `BoundsFound` for the module.
|
||||||
pub fn capture_bounds(module_id: String, element_id: String) -> Task<Message> {
|
pub fn capture_bounds(settings: PopupSettings) -> Task<Message> {
|
||||||
let target = iced::widget::Id::from(element_id);
|
let target = iced::widget::Id::from(settings.element_id.clone());
|
||||||
|
|
||||||
struct FindBounds {
|
struct FindBounds {
|
||||||
target: iced::widget::Id,
|
target: iced::widget::Id,
|
||||||
@@ -98,7 +98,9 @@ pub fn capture_bounds(module_id: String, element_id: String) -> Task<Message> {
|
|||||||
target,
|
target,
|
||||||
found: None,
|
found: None,
|
||||||
})
|
})
|
||||||
.map(move |bounds| Message::Effect(ModuleEffect::BoundsFound(module_id.clone(), bounds)))
|
.map(move |bounds| -> Message {
|
||||||
|
Message::Effect(ModuleEffect::BoundsFound(bounds, settings.clone()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Popup widget tree on its own LayerShell surface; the active module
|
/// Popup widget tree on its own LayerShell surface; the active module
|
||||||
@@ -116,15 +118,32 @@ pub fn view(bar: &Bar) -> Element<'_, Message> {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|| text("unknown module").into());
|
.unwrap_or_else(|| text("unknown module").into());
|
||||||
|
|
||||||
column![
|
container(
|
||||||
text(format!("{} menu", module_id))
|
column![
|
||||||
.size(14)
|
text(format!("{} menu", module_id))
|
||||||
.width(Length::Fill)
|
.size(14)
|
||||||
.align_x(Alignment::Center),
|
.width(Length::Fill)
|
||||||
content,
|
.align_x(Alignment::Center),
|
||||||
mouse_area(text("close").size(12))
|
content,
|
||||||
.on_press(Message::Effect(ModuleEffect::ClosePopup(popup_id))),
|
mouse_area(container(text("close").size(12)).style(pill))
|
||||||
]
|
.on_press(Message::Effect(ModuleEffect::ClosePopup(popup_id))),
|
||||||
.spacing(1)
|
]
|
||||||
|
.spacing(1),
|
||||||
|
)
|
||||||
|
.padding(Padding::from(4))
|
||||||
|
.style(|theme| container::Style {
|
||||||
|
text_color: None,
|
||||||
|
background: Some(
|
||||||
|
iced::Background::Color(theme.extended_palette().background.base.color)
|
||||||
|
.scale_alpha(0.4),
|
||||||
|
),
|
||||||
|
border: Border {
|
||||||
|
width: 3.0,
|
||||||
|
radius: 10.into(),
|
||||||
|
color: theme.extended_palette().secondary.strong.color,
|
||||||
|
},
|
||||||
|
snap: true,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,18 +78,18 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
|
|||||||
/// Runs an effect; may set up bar state for it (e.g. which popup is open).
|
/// Runs an effect; may set up bar state for it (e.g. which popup is open).
|
||||||
fn handle_effect(bar: &mut Bar, effect: ModuleEffect) -> Task<Message> {
|
fn handle_effect(bar: &mut Bar, effect: ModuleEffect) -> Task<Message> {
|
||||||
match effect {
|
match effect {
|
||||||
ModuleEffect::RequestPopup(module_id, element_id) => {
|
ModuleEffect::RequestPopup(settings) => {
|
||||||
// Toggle: close if already open for this module, else open.
|
// Toggle: close if already open for this module, else open.
|
||||||
if bar.active_popup.as_deref() == Some(module_id.as_str()) {
|
if bar.active_popup.as_deref() == Some(settings.module_id.as_str()) {
|
||||||
tracing::debug!(module = %module_id, "closing popup");
|
tracing::debug!(module = settings.module_id, "closing popup");
|
||||||
popup::close_popup(bar)
|
popup::close_popup(bar)
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!(module = %module_id, "opening popup");
|
tracing::debug!(module = settings.module_id, "opening popup");
|
||||||
popup::capture_bounds(module_id, element_id)
|
popup::capture_bounds(settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleEffect::BoundsFound(module_id, bounds) => popup::open_popup(bar, module_id, bounds),
|
ModuleEffect::BoundsFound(bounds, settings) => popup::open_popup(bar, bounds, settings),
|
||||||
|
|
||||||
ModuleEffect::ClosePopup(id) => {
|
ModuleEffect::ClosePopup(id) => {
|
||||||
// Request removal only; keep popup state so the popup content
|
// Request removal only; keep popup state so the popup content
|
||||||
|
|||||||
+16
-21
@@ -3,10 +3,12 @@
|
|||||||
//! service to switch which kind it publishes; right-click opens a popup.
|
//! service to switch which kind it publishes; right-click opens a popup.
|
||||||
|
|
||||||
use iced::widget::{container, mouse_area, text};
|
use iced::widget::{container, mouse_area, text};
|
||||||
use iced::{Element, Padding, Task};
|
use iced::Length::Fill;
|
||||||
|
use iced::{alignment, Element, Padding, Task};
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
pill, BarModule, ClockKind, ClockMsg, ClockPayload, Endpoint, ModuleEffect, Service, Wire,
|
pill, BarModule, ClockKind, ClockMsg, ClockPayload, Endpoint, ModuleEffect, PopupSettings,
|
||||||
|
Service, Wire,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
@@ -82,7 +84,12 @@ impl BarModule for Clock {
|
|||||||
let me = Endpoint::module(self.id());
|
let me = Endpoint::module(self.id());
|
||||||
match window_id {
|
match window_id {
|
||||||
// Popup surface: current time in large text.
|
// Popup surface: current time in large text.
|
||||||
Some(_) => container(text(&self.value).size(56)).padding(20).into(),
|
Some(_) => container(text(&self.value).size(56))
|
||||||
|
.align_x(alignment::Horizontal::Center)
|
||||||
|
.align_y(alignment::Vertical::Center)
|
||||||
|
.padding(20)
|
||||||
|
.width(Fill)
|
||||||
|
.into(),
|
||||||
// Bar surface: clickable time. Container carries the module id
|
// Bar surface: clickable time. Container carries the module id
|
||||||
// so the popup can anchor to these bounds.
|
// so the popup can anchor to these bounds.
|
||||||
None => container(
|
None => container(
|
||||||
@@ -125,10 +132,12 @@ impl BarModule for Clock {
|
|||||||
want,
|
want,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
Some(ClockMsg::OpenPopup) => Task::done(ModuleEffect::RequestPopup(
|
Some(ClockMsg::OpenPopup) => Task::done(ModuleEffect::RequestPopup(PopupSettings {
|
||||||
self.id().to_string(),
|
// name: "barbar".into(),
|
||||||
self.id().to_string(),
|
module_id: self.id().into(),
|
||||||
)),
|
element_id: "clock".into(),
|
||||||
|
gap: 8,
|
||||||
|
})),
|
||||||
None => Task::none(),
|
None => Task::none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,17 +150,3 @@ impl BarModule for Clock {
|
|||||||
Some((POPUP_W, POPUP_H))
|
Some((POPUP_W, POPUP_H))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Regression: `new(None)` used to call `default()`, which called
|
|
||||||
/// `new(None)` — infinite recursion (hard stack overflow) whenever the
|
|
||||||
/// clock config was absent or failed to parse.
|
|
||||||
#[test]
|
|
||||||
fn no_config_terminates() {
|
|
||||||
assert!(!Clock::new(None).show_seconds);
|
|
||||||
assert!(!Clock::default().show_seconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -79,17 +79,12 @@
|
|||||||
# from it, and loads libspa-support from there at runtime.
|
# from it, and loads libspa-support from there at runtime.
|
||||||
pkgs.pipewire.dev
|
pkgs.pipewire.dev
|
||||||
]
|
]
|
||||||
|
# mold for the final link; the flag itself comes from the
|
||||||
|
# repo-wide .cargo/config.toml so dev and nix builds share one.
|
||||||
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [
|
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [
|
||||||
pkgs.mold
|
pkgs.mold
|
||||||
];
|
];
|
||||||
|
|
||||||
# Link with mold (fast linker). Lives in commonArgs so deps and the
|
|
||||||
# final crate share one flag set; note this rebuilds all dependency
|
|
||||||
# crates once when first added.
|
|
||||||
env = pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
|
||||||
RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
|
|
||||||
};
|
|
||||||
|
|
||||||
buildInputs =
|
buildInputs =
|
||||||
guiLibs
|
guiLibs
|
||||||
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
|
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
|
||||||
@@ -144,7 +139,7 @@
|
|||||||
# Put the GUI shared libraries on the runtime library path so winit
|
# Put the GUI shared libraries on the runtime library path so winit
|
||||||
# can dlopen libwayland and wgpu can find the vulkan loader.
|
# can dlopen libwayland and wgpu can find the vulkan loader.
|
||||||
# No RUSTFLAGS here: an env RUSTFLAGS would override (not merge
|
# No RUSTFLAGS here: an env RUSTFLAGS would override (not merge
|
||||||
# with) ~/.cargo/config.toml's rustflags, giving devShell and
|
# with) .cargo/config.toml's rustflags, giving devShell and
|
||||||
# non-devShell builds different fingerprints and force-rebuilding
|
# non-devShell builds different fingerprints and force-rebuilding
|
||||||
# every dependency on each switch.
|
# every dependency on each switch.
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
|||||||
Reference in New Issue
Block a user