feat: add configurable panel background image
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
@@ -9,8 +9,8 @@ use common::{TimestampExt, emojis_from_tags};
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
||||||
Focusable, InteractiveElement, IntoElement, ListAlignment, ListOffset, ListState, MouseButton,
|
Focusable, ImageSource, InteractiveElement, IntoElement, ListAlignment, ListOffset, ListState,
|
||||||
ObjectFit, ParentElement, PathPromptOptions, Render, SharedString, SharedUri,
|
MouseButton, ObjectFit, ParentElement, PathPromptOptions, Render, SharedString, SharedUri,
|
||||||
StatefulInteractiveElement, Styled, StyledImage, Subscription, SystemNotification,
|
StatefulInteractiveElement, Styled, StyledImage, Subscription, SystemNotification,
|
||||||
SystemNotificationAction, Task, WeakEntity, Window, div, img, list, px, red, relative,
|
SystemNotificationAction, Task, WeakEntity, Window, div, img, list, px, red, relative,
|
||||||
retain_all, svg, white,
|
retain_all, svg, white,
|
||||||
@@ -1846,6 +1846,10 @@ impl Panel for ChatPanel {
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn background(&self, cx: &App) -> Option<ImageSource> {
|
||||||
|
AppSettings::panel_background_source(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for ChatPanel {}
|
impl EventEmitter<PanelEvent> for ChatPanel {}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use chat_ui::text::rendered_text;
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
Action, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
FollowMode, IntoElement, ListAlignment, ListScrollEvent, ListState, ParentElement,
|
FollowMode, ImageSource, IntoElement, ListAlignment, ListScrollEvent, ListState, ParentElement,
|
||||||
PathPromptOptions, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, div,
|
PathPromptOptions, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, div,
|
||||||
list, px,
|
list, px,
|
||||||
};
|
};
|
||||||
@@ -1172,6 +1172,10 @@ impl Panel for GroupPanel {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn background(&self, cx: &App) -> Option<ImageSource> {
|
||||||
|
AppSettings::panel_background_source(cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
|
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
|
||||||
let Some(group) = self.group.upgrade() else {
|
let Some(group) = self.group.upgrade() else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Error, anyhow};
|
use anyhow::{Error, anyhow};
|
||||||
use common::config_dir;
|
use common::config_dir;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
use gpui::{App, AppContext, Context, Entity, Global, ImageSource, Subscription, Task};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
@@ -12,6 +14,8 @@ pub fn init(cx: &mut App) {
|
|||||||
|
|
||||||
const DEFAULT_FILE_SERVER: &str = "https://nostr.download/";
|
const DEFAULT_FILE_SERVER: &str = "https://nostr.download/";
|
||||||
const LEGACY_FILE_SERVER: &str = "blossom.band";
|
const LEGACY_FILE_SERVER: &str = "blossom.band";
|
||||||
|
/// Built-in image shown behind chat and group panels
|
||||||
|
pub const DEFAULT_BACKGROUND: &str = "brand/default-bg.jpg";
|
||||||
|
|
||||||
macro_rules! setting_accessors {
|
macro_rules! setting_accessors {
|
||||||
($(pub $field:ident: $type:ty),* $(,)?) => {
|
($(pub $field:ident: $type:ty),* $(,)?) => {
|
||||||
@@ -56,6 +60,8 @@ setting_accessors! {
|
|||||||
pub screening: bool,
|
pub screening: bool,
|
||||||
pub nip4e: bool,
|
pub nip4e: bool,
|
||||||
pub file_server: Url,
|
pub file_server: Url,
|
||||||
|
pub panel_background: bool,
|
||||||
|
pub panel_background_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
setting_getters! {
|
setting_getters! {
|
||||||
@@ -114,12 +120,8 @@ impl RoomConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// Which appearance to paint: follow the OS, or pin light or dark.
|
|
||||||
/// `alias` reads the pre-bezel `theme_mode` key so old config files keep
|
|
||||||
/// their pinned choice.
|
|
||||||
#[serde(default, alias = "theme_mode")]
|
#[serde(default, alias = "theme_mode")]
|
||||||
pub appearance_mode: AppearanceMode,
|
pub appearance_mode: AppearanceMode,
|
||||||
|
|
||||||
@@ -132,6 +134,14 @@ pub struct Settings {
|
|||||||
/// Enable decoupling encryption key
|
/// Enable decoupling encryption key
|
||||||
pub nip4e: bool,
|
pub nip4e: bool,
|
||||||
|
|
||||||
|
/// Show an image behind chat and group panels
|
||||||
|
#[serde(default)]
|
||||||
|
pub panel_background: bool,
|
||||||
|
|
||||||
|
/// Custom image for the panel background; the built-in one when unset
|
||||||
|
#[serde(default)]
|
||||||
|
pub panel_background_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Trusted relays; Coop will automatically authenticate with these relays
|
/// Trusted relays; Coop will automatically authenticate with these relays
|
||||||
pub trusted_relays: Vec<String>,
|
pub trusted_relays: Vec<String>,
|
||||||
|
|
||||||
@@ -154,6 +164,8 @@ impl Default for Settings {
|
|||||||
hide_avatar: false,
|
hide_avatar: false,
|
||||||
screening: true,
|
screening: true,
|
||||||
nip4e: false,
|
nip4e: false,
|
||||||
|
panel_background: true,
|
||||||
|
panel_background_path: None,
|
||||||
trusted_relays: vec![],
|
trusted_relays: vec![],
|
||||||
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
||||||
pinned_rooms: vec![],
|
pinned_rooms: vec![],
|
||||||
@@ -272,6 +284,17 @@ impl AppSettings {
|
|||||||
self.inner.read(cx).nip4e
|
self.inner.read(cx).nip4e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The image to show behind chat and group panels, when enabled
|
||||||
|
pub fn panel_background_source(cx: &App) -> Option<ImageSource> {
|
||||||
|
if !Self::get_panel_background(cx) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(match Self::get_panel_background_path(cx) {
|
||||||
|
Some(path) => path.into(),
|
||||||
|
None => DEFAULT_BACKGROUND.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if the chat room is pinned
|
/// Check if the chat room is pinned
|
||||||
pub fn pinned_room(&self, id: u64, cx: &App) -> bool {
|
pub fn pinned_room(&self, id: u64, cx: &App) -> bool {
|
||||||
self.inner.read(cx).pinned_rooms.contains(&id)
|
self.inner.read(cx).pinned_rooms.contains(&id)
|
||||||
|
|||||||
+46
-24
@@ -6,10 +6,10 @@ use std::sync::Arc;
|
|||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, App, AppContext as _, Bounds, Context, Div, Empty, Entity,
|
AnyElement, AnyView, App, AppContext as _, Bounds, Context, Div, Empty, Entity,
|
||||||
GlobalElementId, InspectorElementId, InteractiveElement as _, IntoElement, LayoutId,
|
GlobalElementId, ImageSource, InspectorElementId, InteractiveElement as _, IntoElement,
|
||||||
MouseButton, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Render, ScrollHandle,
|
LayoutId, MouseButton, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Render,
|
||||||
SharedString, Stateful, StatefulInteractiveElement as _, Style, StyleRefinement, Styled as _,
|
ScrollHandle, SharedString, Stateful, StatefulInteractiveElement as _, Style, StyleRefinement,
|
||||||
WeakEntity, Window, actions, div, px,
|
Styled as _, WeakEntity, Window, actions, div, px,
|
||||||
};
|
};
|
||||||
pub use gpui_base::dock::{DockArea, DockLayout, DockPlacement};
|
pub use gpui_base::dock::{DockArea, DockLayout, DockPlacement};
|
||||||
use gpui_base::dock::{
|
use gpui_base::dock::{
|
||||||
@@ -24,7 +24,7 @@ use crate::resizable::{resize_handle, resize_handle_appearance};
|
|||||||
use crate::tab::Tab;
|
use crate::tab::Tab;
|
||||||
use crate::tab::tab_bar::TabBar;
|
use crate::tab::tab_bar::TabBar;
|
||||||
use crate::title_bar::{TRAFFIC_LIGHT_PADDING, title_bar_drag_handlers, window_controls};
|
use crate::title_bar::{TRAFFIC_LIGHT_PADDING, title_bar_drag_handlers, window_controls};
|
||||||
use crate::{IconName, Selectable, Sizable, StyledExt, h_flex, v_flex};
|
use crate::{IconName, Selectable, Sizable, StyledExt, background, h_flex, v_flex};
|
||||||
|
|
||||||
mod panel;
|
mod panel;
|
||||||
pub use panel::*;
|
pub use panel::*;
|
||||||
@@ -431,6 +431,11 @@ impl TabGroupSkin {
|
|||||||
let tabs_count = group.panels().len();
|
let tabs_count = group.panels().len();
|
||||||
let is_title_bar = self.is_title_bar_group(group, cx);
|
let is_title_bar = self.is_title_bar_group(group, cx);
|
||||||
|
|
||||||
|
let wallpaper = group
|
||||||
|
.active_panel()
|
||||||
|
.and_then(|panel| panel_background(panel, cx))
|
||||||
|
.is_some();
|
||||||
|
|
||||||
let displayed = group.active_panel().map(|panel| panel.panel_id(cx));
|
let displayed = group.active_panel().map(|panel| panel.panel_id(cx));
|
||||||
let displayed_ix = displayed.and_then(|displayed| {
|
let displayed_ix = displayed.and_then(|displayed| {
|
||||||
group
|
group
|
||||||
@@ -467,7 +472,7 @@ impl TabGroupSkin {
|
|||||||
let bar = TabBar::new("tab-bar")
|
let bar = TabBar::new("tab-bar")
|
||||||
.track_scroll(&self.scroll_handle)
|
.track_scroll(&self.scroll_handle)
|
||||||
.h(TABBAR_HEIGHT)
|
.h(TABBAR_HEIGHT)
|
||||||
.bg(cx.theme().bg)
|
.when(!wallpaper, |this| this.bg(cx.theme().bg))
|
||||||
.when(needs_traffic_light_padding, |this| {
|
.when(needs_traffic_light_padding, |this| {
|
||||||
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
||||||
})
|
})
|
||||||
@@ -569,7 +574,7 @@ impl TabGroupSkin {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.h(TABBAR_HEIGHT)
|
.h(TABBAR_HEIGHT)
|
||||||
.w_full()
|
.w_full()
|
||||||
.bg(cx.theme().bg)
|
.when(!wallpaper, |this| this.bg(cx.theme().bg))
|
||||||
.child(bar.flex_1())
|
.child(bar.flex_1())
|
||||||
.child(window_controls())
|
.child(window_controls())
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
@@ -647,23 +652,31 @@ impl TabGroupSkin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TabGroupRenderer for TabGroupSkin {
|
impl TabGroupRenderer for TabGroupSkin {
|
||||||
fn frame(&self, group: &TabGroupContext, _: &mut Window, _cx: &mut App) -> Stateful<Div> {
|
fn frame(&self, group: &TabGroupContext, _: &mut Window, cx: &mut App) -> Stateful<Div> {
|
||||||
div().id("tab-panel").when(!group.is_collapsed(), |this| {
|
let wallpaper = group
|
||||||
this.on_action({
|
.active_panel()
|
||||||
let group = TabGroupContext::clone(group);
|
.and_then(|panel| panel_background(panel, cx));
|
||||||
move |_: &ToggleZoom, window, cx| group.toggle_zoom(window, cx)
|
|
||||||
|
div()
|
||||||
|
.id("tab-panel")
|
||||||
|
.relative()
|
||||||
|
.when_some(wallpaper, |this, source| this.child(background(source, cx)))
|
||||||
|
.when(!group.is_collapsed(), |this| {
|
||||||
|
this.on_action({
|
||||||
|
let group = TabGroupContext::clone(group);
|
||||||
|
move |_: &ToggleZoom, window, cx| group.toggle_zoom(window, cx)
|
||||||
|
})
|
||||||
|
.on_action({
|
||||||
|
let group = TabGroupContext::clone(group);
|
||||||
|
move |_: &ClosePanel, window, cx| {
|
||||||
|
let Some(panel) = group.active_panel() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let panel = panel.panel_id(cx);
|
||||||
|
group.close(panel, window, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.on_action({
|
|
||||||
let group = TabGroupContext::clone(group);
|
|
||||||
move |_: &ClosePanel, window, cx| {
|
|
||||||
let Some(panel) = group.active_panel() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let panel = panel.panel_id(cx);
|
|
||||||
group.close(panel, window, cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab_bar(
|
fn render_tab_bar(
|
||||||
@@ -710,6 +723,11 @@ impl TabGroupRenderer for TabGroupSkin {
|
|||||||
return Empty.into_any_element();
|
return Empty.into_any_element();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wallpaper = group
|
||||||
|
.active_panel()
|
||||||
|
.and_then(|active| panel_background(active, cx))
|
||||||
|
.is_some();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("tab-content")
|
.id("tab-content")
|
||||||
.group("")
|
.group("")
|
||||||
@@ -718,7 +736,7 @@ impl TabGroupRenderer for TabGroupSkin {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.bg(cx.theme().bg)
|
.when(!wallpaper, |this| this.bg(cx.theme().bg))
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(panel.cached(StyleRefinement::default().v_flex().size_full())),
|
.child(panel.cached(StyleRefinement::default().v_flex().size_full())),
|
||||||
)
|
)
|
||||||
@@ -781,3 +799,7 @@ fn panel_title(panel: &Arc<dyn gpui_base::dock::PanelView>, cx: &App) -> AnyElem
|
|||||||
None => SharedString::from(panel.panel_name(cx)).into_any_element(),
|
None => SharedString::from(panel.panel_name(cx)).into_any_element(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn panel_background(panel: &Arc<dyn gpui_base::dock::PanelView>, cx: &App) -> Option<ImageSource> {
|
||||||
|
PanelHandle::of(panel).and_then(|handle| handle.panel().background(cx))
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::any::Any;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, App, Element, Entity, EventEmitter, FocusHandle, Focusable, Render,
|
AnyElement, AnyView, App, Element, Entity, EventEmitter, FocusHandle, Focusable, ImageSource,
|
||||||
SharedString, Window,
|
Render, SharedString, Window,
|
||||||
};
|
};
|
||||||
use gpui_base::dock::{PanelId, PanelState};
|
use gpui_base::dock::{PanelId, PanelState};
|
||||||
|
|
||||||
@@ -46,6 +46,11 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
|
|||||||
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
|
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An optional image drawn behind the panel and its tab bar.
|
||||||
|
fn background(&self, _cx: &App) -> Option<ImageSource> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait PanelView: 'static + Send + Sync {
|
pub(crate) trait PanelView: 'static + Send + Sync {
|
||||||
@@ -54,6 +59,7 @@ pub(crate) trait PanelView: 'static + Send + Sync {
|
|||||||
fn closable(&self, cx: &App) -> bool;
|
fn closable(&self, cx: &App) -> bool;
|
||||||
fn zoomable(&self, cx: &App) -> bool;
|
fn zoomable(&self, cx: &App) -> bool;
|
||||||
fn visible(&self, cx: &App) -> bool;
|
fn visible(&self, cx: &App) -> bool;
|
||||||
|
fn background(&self, cx: &App) -> Option<ImageSource>;
|
||||||
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
|
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
|
||||||
fn set_zoomed(&self, zoomed: bool, cx: &mut App);
|
fn set_zoomed(&self, zoomed: bool, cx: &mut App);
|
||||||
fn view(&self) -> AnyView;
|
fn view(&self) -> AnyView;
|
||||||
@@ -81,6 +87,10 @@ impl<T: Panel> PanelView for Entity<T> {
|
|||||||
self.read(cx).visible(cx)
|
self.read(cx).visible(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn background(&self, cx: &App) -> Option<ImageSource> {
|
||||||
|
self.read(cx).background(cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
|
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
|
||||||
self.update(cx, |this, cx| {
|
self.update(cx, |this, cx| {
|
||||||
this.set_active(active, window, cx);
|
this.set_active(active, window, cx);
|
||||||
|
|||||||
+17
-1
@@ -1,4 +1,7 @@
|
|||||||
use gpui::{App, Div, Pixels, Refineable, StyleRefinement, Styled, div, px};
|
use gpui::{
|
||||||
|
App, Div, ImageSource, ObjectFit, ParentElement, Pixels, Refineable, StyleRefinement, Styled,
|
||||||
|
StyledImage, div, img, px,
|
||||||
|
};
|
||||||
pub use gpui_base::component_traits::{Disableable, Selectable};
|
pub use gpui_base::component_traits::{Disableable, Selectable};
|
||||||
use theme::{ActiveTheme, TextStyle, Typeset as _, surface_radius, surface_shadows};
|
use theme::{ActiveTheme, TextStyle, Typeset as _, surface_radius, surface_shadows};
|
||||||
|
|
||||||
@@ -17,6 +20,19 @@ pub fn divider(cx: &App) -> Div {
|
|||||||
div().w_full().h_px().bg(cx.theme().border_faint)
|
div().w_full().h_px().bg(cx.theme().border_faint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a full-bleed image with a wash for readability, to show behind panel content.
|
||||||
|
pub fn background<I>(image: I, cx: &App) -> Div
|
||||||
|
where
|
||||||
|
I: Into<ImageSource>,
|
||||||
|
{
|
||||||
|
let wash = if cx.theme().is_dark() { 0.5 } else { 0.2 };
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.inset_0()
|
||||||
|
.child(img(image).size_full().object_fit(ObjectFit::Cover))
|
||||||
|
.child(div().absolute().inset_0().bg(cx.theme().bg.opacity(wash)))
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! font_weight {
|
macro_rules! font_weight {
|
||||||
($fn:ident, $const:ident) => {
|
($fn:ident, $const:ident) => {
|
||||||
/// [docs](https://tailwindcss.com/docs/font-weight)
|
/// [docs](https://tailwindcss.com/docs/font-weight)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use gpui::http_client::Url;
|
use gpui::http_client::Url;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
|
App, AppContext, Context, Entity, IntoElement, ParentElement, PathPromptOptions, Render,
|
||||||
Window, div,
|
SharedString, Styled, Window, div,
|
||||||
};
|
};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use theme::{ActiveTheme, AppExt as _, AppearanceMode, TextStyle, Typeset as _};
|
use theme::{ActiveTheme, AppExt as _, AppearanceMode, TextStyle, Typeset as _};
|
||||||
@@ -11,7 +11,7 @@ use ui::input::{Input, InputState};
|
|||||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::switch::Switch;
|
use ui::switch::Switch;
|
||||||
use ui::{IconName, Sizable, WindowExtension, h_flex, v_flex};
|
use ui::{Disableable, IconName, Sizable, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
|
||||||
cx.new(|cx| Preferences::new(window, cx))
|
cx.new(|cx| Preferences::new(window, cx))
|
||||||
@@ -52,6 +52,32 @@ impl Preferences {
|
|||||||
AppSettings::update_appearance_mode(mode, cx);
|
AppSettings::update_appearance_mode(mode, cx);
|
||||||
cx.set_appearance_mode(mode);
|
cx.set_appearance_mode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ask the user for an image to show behind chat and group panels
|
||||||
|
fn choose_background(cx: &mut App) {
|
||||||
|
let task = cx.prompt_for_paths(PathPromptOptions {
|
||||||
|
files: true,
|
||||||
|
directories: false,
|
||||||
|
multiple: false,
|
||||||
|
prompt: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let Ok(Ok(Some(mut paths))) = task.await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(path) = paths.pop() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
AppSettings::update_panel_background_path(Some(path), cx);
|
||||||
|
cx.refresh_windows();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Preferences {
|
impl Render for Preferences {
|
||||||
@@ -60,11 +86,19 @@ impl Render for Preferences {
|
|||||||
const AVATAR: &str = "Hide all avatar pictures to improve performance.";
|
const AVATAR: &str = "Hide all avatar pictures to improve performance.";
|
||||||
const MODE: &str = "Follow the OS, or always use light or dark.";
|
const MODE: &str = "Follow the OS, or always use light or dark.";
|
||||||
const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages.";
|
const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages.";
|
||||||
|
const BACKGROUND: &str = "Show an image behind chat and group panels.";
|
||||||
|
|
||||||
let screening = AppSettings::get_screening(cx);
|
let screening = AppSettings::get_screening(cx);
|
||||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
let nip4e = AppSettings::get_nip4e(cx);
|
let nip4e = AppSettings::get_nip4e(cx);
|
||||||
let appearance_mode = AppSettings::get_appearance_mode(cx);
|
let appearance_mode = AppSettings::get_appearance_mode(cx);
|
||||||
|
let panel_background = AppSettings::get_panel_background(cx);
|
||||||
|
let panel_background_path = AppSettings::get_panel_background_path(cx);
|
||||||
|
|
||||||
|
let background_label = match &panel_background_path {
|
||||||
|
Some(path) => path.display().to_string(),
|
||||||
|
None => String::from("Default Background"),
|
||||||
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
@@ -132,6 +166,73 @@ impl Render for Preferences {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
GroupBox::new()
|
||||||
|
.id("background")
|
||||||
|
.title("Background")
|
||||||
|
.fill()
|
||||||
|
.child(
|
||||||
|
Switch::new("panel-background")
|
||||||
|
.label("Panel background")
|
||||||
|
.description(BACKGROUND)
|
||||||
|
.checked(panel_background)
|
||||||
|
.on_click(move |_, _window, cx| {
|
||||||
|
AppSettings::update_panel_background(!panel_background, cx);
|
||||||
|
cx.refresh_windows();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_3()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.flex_1()
|
||||||
|
.min_w_0()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_style(TextStyle::Callout)
|
||||||
|
.child(SharedString::from("Image")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_style(TextStyle::Caption)
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.truncate()
|
||||||
|
.child(SharedString::from(background_label)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Button::new("choose-background")
|
||||||
|
.label("Choose image")
|
||||||
|
.ghost_alt()
|
||||||
|
.small()
|
||||||
|
.disabled(!panel_background)
|
||||||
|
.on_click(|_, _window, cx| {
|
||||||
|
Self::choose_background(cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("reset-background")
|
||||||
|
.label("Reset")
|
||||||
|
.ghost_alt()
|
||||||
|
.small()
|
||||||
|
.disabled(
|
||||||
|
!panel_background
|
||||||
|
|| panel_background_path.is_none(),
|
||||||
|
)
|
||||||
|
.on_click(|_, _window, cx| {
|
||||||
|
AppSettings::update_panel_background_path(None, cx);
|
||||||
|
cx.refresh_windows();
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
GroupBox::new()
|
GroupBox::new()
|
||||||
.id("experiments")
|
.id("experiments")
|
||||||
|
|||||||
Reference in New Issue
Block a user