Let the sidebar divider be dragged
This commit is contained in:
44
src/app.rs
44
src/app.rs
@@ -25,7 +25,7 @@ use crate::settings::{
|
||||
StreamingCacheBudget,
|
||||
};
|
||||
use iced::widget::{markdown, scrollable, text_input};
|
||||
use iced::{Size, Subscription, Task, keyboard, window};
|
||||
use iced::{Size, Subscription, Task, keyboard, mouse, window};
|
||||
use rfd::AsyncFileDialog;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::fs;
|
||||
@@ -38,6 +38,8 @@ use std::time::{Duration, Instant};
|
||||
|
||||
const APP_ID: &str = "de.rfc1437.ds4server";
|
||||
const METRICS_SAMPLE_INTERVAL: Duration = Duration::from_millis(200);
|
||||
pub(super) const MIN_SIDEBAR_WIDTH: i32 = 180;
|
||||
pub(super) const MAX_SIDEBAR_WIDTH: i32 = 520;
|
||||
|
||||
pub(crate) struct App {
|
||||
main_window: window::Id,
|
||||
@@ -64,6 +66,8 @@ pub(crate) struct App {
|
||||
session_rename: Option<(i32, String)>,
|
||||
/// Projects whose archived sessions are expanded in the sidebar.
|
||||
expanded_archives: HashSet<i32>,
|
||||
/// True while the sidebar divider is being dragged.
|
||||
sidebar_drag: bool,
|
||||
choosing_folder: bool,
|
||||
pending_project_path: Option<PathBuf>,
|
||||
project_name_input: String,
|
||||
@@ -194,6 +198,9 @@ pub(crate) enum Message {
|
||||
ShowChat,
|
||||
ShowStats,
|
||||
ToggleSidebar,
|
||||
StartSidebarDrag,
|
||||
DragSidebar(f32),
|
||||
EndSidebarDrag,
|
||||
MetricsTick,
|
||||
}
|
||||
|
||||
@@ -242,6 +249,7 @@ impl App {
|
||||
session_menu: None,
|
||||
session_rename: None,
|
||||
expanded_archives: HashSet::new(),
|
||||
sidebar_drag: false,
|
||||
choosing_folder: false,
|
||||
pending_project_path: None,
|
||||
project_name_input: String::new(),
|
||||
@@ -323,6 +331,7 @@ impl App {
|
||||
session_menu: None,
|
||||
session_rename: None,
|
||||
expanded_archives: HashSet::new(),
|
||||
sidebar_drag: false,
|
||||
choosing_folder: false,
|
||||
pending_project_path: None,
|
||||
project_name_input: String::new(),
|
||||
@@ -414,6 +423,26 @@ impl App {
|
||||
}
|
||||
Message::ShowChat => self.detail_tab = DetailTab::Chat,
|
||||
Message::ShowStats => self.detail_tab = DetailTab::Stats,
|
||||
// The sidebar starts at the window's left edge, so the cursor's x is
|
||||
// the width the user is asking for. Only the final width is stored.
|
||||
Message::StartSidebarDrag => self.sidebar_drag = true,
|
||||
Message::DragSidebar(x) => {
|
||||
if self.sidebar_drag {
|
||||
self.preferences.sidebar_width =
|
||||
(x.round() as i32).clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH);
|
||||
}
|
||||
}
|
||||
Message::EndSidebarDrag => {
|
||||
if self.sidebar_drag {
|
||||
self.sidebar_drag = false;
|
||||
if let Some(database) = self.database.as_mut()
|
||||
&& let Err(error) =
|
||||
database.set_sidebar_width(self.preferences.sidebar_width)
|
||||
{
|
||||
self.error = Some(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ToggleSidebar => {
|
||||
let collapsed = !self.preferences.sidebar_collapsed;
|
||||
self.preferences.sidebar_collapsed = collapsed;
|
||||
@@ -969,6 +998,19 @@ impl App {
|
||||
iced::time::every(Duration::from_millis(50)).map(|_| Message::GenerationTick),
|
||||
);
|
||||
}
|
||||
// The cursor leaves the thin divider as soon as it moves, so the drag is
|
||||
// followed through raw window events instead of the widget.
|
||||
if self.sidebar_drag {
|
||||
subscriptions.push(iced::event::listen_with(|event, _, _| match event {
|
||||
iced::Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
||||
Some(Message::DragSidebar(position.x))
|
||||
}
|
||||
iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||
Some(Message::EndSidebarDrag)
|
||||
}
|
||||
_ => None,
|
||||
}));
|
||||
}
|
||||
Subscription::batch(subscriptions)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ mod stats;
|
||||
use model_manager::{download_status_bar, format_bytes, format_duration};
|
||||
|
||||
use super::{
|
||||
ActiveDownload, App, DetailTab, Message, MetricsPoint, ModelDownload, ModelOperation,
|
||||
chat_scroll_id, composer_id, models_path,
|
||||
ActiveDownload, App, DetailTab, MAX_SIDEBAR_WIDTH, MIN_SIDEBAR_WIDTH, Message, MetricsPoint,
|
||||
ModelDownload, ModelOperation, chat_scroll_id, composer_id, models_path,
|
||||
};
|
||||
use crate::database::{ProjectWithSessions, Session, SessionState};
|
||||
use crate::model::{
|
||||
@@ -17,7 +17,8 @@ use crate::settings::{GIB, REASONING_MODES};
|
||||
use iced::theme::{Palette, palette};
|
||||
use iced::widget::{
|
||||
Button, Space, Svg, Tooltip, button, checkbox, column, container, horizontal_rule, markdown,
|
||||
opaque, pick_list, progress_bar, row, scrollable, stack, svg, text, text_input, tooltip,
|
||||
mouse_area, opaque, pick_list, progress_bar, row, scrollable, stack, svg, text, text_input,
|
||||
tooltip,
|
||||
};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Theme, window};
|
||||
use std::collections::VecDeque;
|
||||
@@ -65,6 +66,17 @@ impl App {
|
||||
let mut body = row![].width(Length::Fill).height(Length::Fill);
|
||||
if !self.preferences.sidebar_collapsed {
|
||||
body = body.push(self.sidebar());
|
||||
body = body.push(
|
||||
mouse_area(
|
||||
container(Space::with_width(Length::Fill))
|
||||
.width(5)
|
||||
.height(Length::Fill)
|
||||
.style(divider_style),
|
||||
)
|
||||
.interaction(iced::mouse::Interaction::ResizingHorizontally)
|
||||
.on_press(Message::StartSidebarDrag)
|
||||
.on_release(Message::EndSidebarDrag),
|
||||
);
|
||||
}
|
||||
body = body.push(
|
||||
container(self.detail())
|
||||
@@ -259,7 +271,11 @@ impl App {
|
||||
]
|
||||
.spacing(10),
|
||||
)
|
||||
.width(276)
|
||||
.width(
|
||||
self.preferences
|
||||
.sidebar_width
|
||||
.clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH) as f32,
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.padding(Padding::new(16.0).top(16.0 + TITLE_BAR_HEIGHT))
|
||||
.style(sidebar_style)
|
||||
@@ -808,6 +824,10 @@ fn muted_text() -> Color {
|
||||
Color::from_rgb8(174, 174, 178)
|
||||
}
|
||||
|
||||
fn divider_style(_: &Theme) -> container::Style {
|
||||
container::Style::default().background(Color::from_rgb8(38, 38, 40))
|
||||
}
|
||||
|
||||
fn sidebar_style(_: &Theme) -> container::Style {
|
||||
container::Style::default().background(Color::from_rgb8(23, 23, 25))
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ pub struct AppPreferences {
|
||||
pub sidebar_collapsed: bool,
|
||||
/// Project the app reopens on. Cleared when that project goes away.
|
||||
pub last_project_id: Option<i32>,
|
||||
pub sidebar_width: i32,
|
||||
}
|
||||
|
||||
impl Default for AppPreferences {
|
||||
@@ -101,6 +102,7 @@ impl Default for AppPreferences {
|
||||
endpoint_cors: false,
|
||||
sidebar_collapsed: false,
|
||||
last_project_id: None,
|
||||
sidebar_width: 276,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +448,14 @@ impl Database {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn set_sidebar_width(&mut self, width: i32) -> Result<(), String> {
|
||||
diesel::update(preferences::table.find(1))
|
||||
.set(preferences::sidebar_width.eq(width))
|
||||
.execute(&mut self.connection)
|
||||
.map(|_| ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn set_last_project(&mut self, project_id: Option<i32>) -> Result<(), String> {
|
||||
diesel::update(preferences::table.find(1))
|
||||
.set(preferences::last_project_id.eq(project_id))
|
||||
@@ -877,6 +887,7 @@ mod tests {
|
||||
|
||||
database.set_sidebar_collapsed(true).unwrap();
|
||||
database.set_last_project(Some(project.id)).unwrap();
|
||||
database.set_sidebar_width(320).unwrap();
|
||||
assert!(!loaded[0].project.collapsed);
|
||||
database.set_project_collapsed(project.id, true).unwrap();
|
||||
assert!(database.load_projects().unwrap()[0].project.collapsed);
|
||||
@@ -889,6 +900,7 @@ mod tests {
|
||||
let preferences = reopened.load_preferences().unwrap();
|
||||
assert!(preferences.sidebar_collapsed);
|
||||
assert_eq!(preferences.last_project_id, Some(project.id));
|
||||
assert_eq!(preferences.sidebar_width, 320);
|
||||
assert_eq!(preferences.selected_model, "glm-5.2");
|
||||
assert_eq!(preferences.idle_timeout_minutes, 30);
|
||||
assert_eq!(preferences.endpoint_port, 4567);
|
||||
|
||||
@@ -60,6 +60,7 @@ diesel::table! {
|
||||
endpoint_cors -> Bool,
|
||||
sidebar_collapsed -> Bool,
|
||||
last_project_id -> Nullable<Integer>,
|
||||
sidebar_width -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user