diff --git a/core/src/activity.rs b/core/src/activity.rs index 30bbb9ba9e..da659a5fdd 100644 --- a/core/src/activity.rs +++ b/core/src/activity.rs @@ -148,20 +148,7 @@ impl Widget for ActivityBar { .clone(); if let Some(panel) = data.panels.get(&PanelPosition::LeftTop) { for (widget_id, kind) in panel.widgets.iter() { - let svg = match kind { - crate::data::PanelKind::FileExplorer => { - get_svg("file-explorer.svg").unwrap() - } - crate::data::PanelKind::SourceControl => { - get_svg("git-icon.svg").unwrap() - } - crate::data::PanelKind::Plugin => { - get_svg("plugin-icon.svg").unwrap() - } - crate::data::PanelKind::Terminal => { - get_svg("terminal.svg").unwrap() - } - }; + let svg = kind.svg(); if &panel.active == widget_id { ctx.fill( Size::new(size, size) diff --git a/core/src/command.rs b/core/src/command.rs index e74b028468..ea4c093369 100644 --- a/core/src/command.rs +++ b/core/src/command.rs @@ -144,6 +144,18 @@ pub enum LapceWorkbenchCommand { #[strum(serialize = "toggle_maximized_panel")] ToggleMaximizedPanel, + #[strum(serialize = "toggle_source_control")] + ToggleSourceControl, + + #[strum(serialize = "toggle_plugin")] + TogglePlugin, + + #[strum(serialize = "toggle_file_explorer")] + ToggleFileExplorer, + + #[strum(serialize = "toggle_problem")] + ToggleProblem, + #[strum(serialize = "focus_editor")] FocusEditor, diff --git a/core/src/data.rs b/core/src/data.rs index 43cd640c38..c9684d9f2c 100644 --- a/core/src/data.rs +++ b/core/src/data.rs @@ -17,7 +17,7 @@ use crossbeam_channel::{bounded, unbounded, Receiver, Sender, TryRecvError}; use crossbeam_utils::sync::WaitGroup; use directories::{ProjectDirs, UserDirs}; use druid::{ - piet::{PietText, Text}, + piet::{PietText, Svg, Text}, theme, widget::{Label, LabelText}, Application, Color, Command, Data, Env, EventCtx, ExtEventSink, FontDescriptor, @@ -71,9 +71,11 @@ use crate::{ palette::{PaletteData, PaletteType, PaletteViewData}, panel::PanelPosition, plugin::PluginData, + problem::ProblemData, proxy::{LapceProxy, ProxyHandlerNew, TermEvent}, source_control::{SourceControlData, SOURCE_CONTROL_BUFFER}, state::{LapceWorkspace, LapceWorkspaceType, Mode, VisualMode}, + svg::get_svg, terminal::TerminalSplitData, }; @@ -208,12 +210,29 @@ pub struct EditorDiagnostic { pub diagnositc: Diagnostic, } -#[derive(Clone)] +#[derive(Clone, PartialEq, Data)] pub enum PanelKind { FileExplorer, SourceControl, Plugin, Terminal, + Problem, +} + +impl PanelKind { + pub fn svg_name(&self) -> String { + match &self { + PanelKind::FileExplorer => "file-explorer.svg".to_string(), + PanelKind::SourceControl => "git-icon.svg".to_string(), + PanelKind::Plugin => "plugin-icon.svg".to_string(), + PanelKind::Terminal => "terminal.svg".to_string(), + PanelKind::Problem => "error.svg".to_string(), + } + } + + pub fn svg(&self) -> Svg { + get_svg(&self.svg_name()).unwrap() + } } #[derive(Clone)] @@ -284,9 +303,8 @@ pub struct WorkProgress { #[derive(Clone, PartialEq, Data)] pub enum FocusArea { Palette, - SourceControl, Editor, - Terminal, + Panel(PanelKind), } #[derive(Clone, Lens)] @@ -299,6 +317,7 @@ pub struct LapceTabData { pub palette: Arc, pub find: Arc, pub source_control: Arc, + pub problem: Arc, pub plugin: Arc, pub plugins: Arc>, pub installed_plugins: Arc>, @@ -339,6 +358,7 @@ impl Data for LapceTabData { && self.progresses.ptr_eq(&other.progresses) && self.file_explorer.same(&other.file_explorer) && self.plugin.same(&other.plugin) + && self.problem.same(&other.problem) && self.installed_plugins.same(&other.installed_plugins) } } @@ -393,6 +413,7 @@ impl LapceTabData { ); let terminal = Arc::new(TerminalSplitData::new(proxy.clone())); + let problem = Arc::new(ProblemData::new()); let mut panels = im::HashMap::new(); panels.insert( @@ -412,7 +433,10 @@ impl LapceTabData { PanelPosition::BottomLeft, Arc::new(PanelData { active: terminal.widget_id, - widgets: vec![(terminal.widget_id, PanelKind::Terminal)], + widgets: vec![ + (terminal.widget_id, PanelKind::Terminal), + (problem.widget_id, PanelKind::Problem), + ], shown: true, maximized: false, }), @@ -425,6 +449,7 @@ impl LapceTabData { completion, terminal, plugin, + problem, plugins: Arc::new(Vec::new()), installed_plugins: Arc::new(HashMap::new()), find: Arc::new(Find::new(0)), @@ -845,7 +870,7 @@ impl LapceTabData { )); } LapceWorkbenchCommand::ToggleTerminal => { - if self.focus_area == FocusArea::Terminal { + if self.focus_area == FocusArea::Panel(PanelKind::Terminal) { for (_, panel) in self.panels.iter_mut() { if panel .widgets @@ -911,6 +936,18 @@ impl LapceTabData { Target::Widget(self.terminal.active), )); } + LapceWorkbenchCommand::ToggleSourceControl => { + self.toggle_panel( + ctx, + self.source_control.widget_id, + PanelKind::SourceControl, + ); + } + LapceWorkbenchCommand::TogglePlugin => {} + LapceWorkbenchCommand::ToggleFileExplorer => {} + LapceWorkbenchCommand::ToggleProblem => { + self.toggle_panel(ctx, self.problem.widget_id, PanelKind::Problem); + } } } @@ -991,6 +1028,52 @@ impl LapceTabData { } } + fn toggle_panel( + &mut self, + ctx: &mut EventCtx, + widget_id: WidgetId, + kind: PanelKind, + ) { + if self.focus_area == FocusArea::Panel(kind) { + for (_, panel) in self.panels.iter_mut() { + if panel + .widgets + .iter() + .map(|(id, kind)| *id) + .contains(&widget_id) + { + let panel = Arc::make_mut(panel); + panel.shown = false; + break; + } + } + ctx.submit_command(Command::new( + LAPCE_UI_COMMAND, + LapceUICommand::Focus, + Target::Widget(*self.main_split.active), + )); + } else { + for (_, panel) in self.panels.iter_mut() { + if panel + .widgets + .iter() + .map(|(id, kind)| *id) + .contains(&widget_id) + { + let panel = Arc::make_mut(panel); + panel.shown = true; + panel.active = widget_id; + ctx.submit_command(Command::new( + LAPCE_UI_COMMAND, + LapceUICommand::Focus, + Target::Widget(widget_id), + )); + break; + } + } + } + } + pub fn buffer_update_process( tab_id: WidgetId, receiver: Receiver, diff --git a/core/src/lib.rs b/core/src/lib.rs index a9f6414953..1284d5daab 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,6 +19,7 @@ pub mod outline; pub mod palette; pub mod panel; pub mod plugin; +pub mod problem; pub mod proxy; pub mod scroll; pub mod signature; diff --git a/core/src/palette.rs b/core/src/palette.rs index 95d3a5c9ab..9142e47e6a 100644 --- a/core/src/palette.rs +++ b/core/src/palette.rs @@ -44,7 +44,7 @@ use crate::{ config::{Config, LapceTheme}, data::{ EditorContent, EditorKind, FocusArea, LapceEditorData, LapceEditorViewData, - LapceMainSplitData, LapceTabData, + LapceMainSplitData, LapceTabData, PanelKind, }, editor::{EditorLocationNew, LapceEditorContainer, LapceEditorView}, find::Find, @@ -859,7 +859,7 @@ impl PaletteViewData { } fn get_lines(&mut self, ctx: &mut EventCtx) { - if self.focus_area == FocusArea::Terminal { + if self.focus_area == FocusArea::Panel(PanelKind::Terminal) { if let Some(terminal) = self.terminal.terminals.get(&self.terminal.active_term_id) { diff --git a/core/src/problem.rs b/core/src/problem.rs new file mode 100644 index 0000000000..33e1c1a680 --- /dev/null +++ b/core/src/problem.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use druid::{ + piet::{Text, TextAttribute, TextLayout as PietTextLayout, TextLayoutBuilder}, + theme, + widget::{CrossAxisAlignment, Flex, FlexParams, Label, Scroll, SvgData}, + Affine, BoxConstraints, Color, Command, Cursor, Data, Env, Event, EventCtx, + FontFamily, FontWeight, LayoutCtx, LifeCycle, LifeCycleCtx, MouseEvent, + PaintCtx, Point, Rect, RenderContext, Size, Target, TextLayout, UpdateCtx, Vec2, + Widget, WidgetExt, WidgetId, WidgetPod, WindowId, +}; + +use crate::{ + command::{LapceUICommand, LAPCE_UI_COMMAND}, + data::{FocusArea, LapceTabData, PanelKind}, +}; + +pub struct ProblemData { + pub widget_id: WidgetId, +} + +impl ProblemData { + pub fn new() -> Self { + Self { + widget_id: WidgetId::next(), + } + } +} + +pub struct Problem { + pub widget_id: WidgetId, +} + +impl Problem { + pub fn new(data: &LapceTabData) -> Self { + Self { + widget_id: data.problem.widget_id, + } + } + + pub fn request_focus(&self, ctx: &mut EventCtx, data: &mut LapceTabData) { + ctx.request_focus(); + data.focus = self.widget_id; + data.focus_area = FocusArea::Panel(PanelKind::Problem); + } +} + +impl Widget for Problem { + fn id(&self) -> Option { + Some(self.widget_id) + } + + fn event( + &mut self, + ctx: &mut EventCtx, + event: &Event, + data: &mut LapceTabData, + env: &Env, + ) { + match event { + Event::MouseDown(mouse_event) => { + self.request_focus(ctx, data); + } + Event::Command(cmd) if cmd.is(LAPCE_UI_COMMAND) => { + let command = cmd.get_unchecked(LAPCE_UI_COMMAND); + match command { + LapceUICommand::Focus => { + self.request_focus(ctx, data); + ctx.set_handled(); + } + _ => (), + } + } + _ => (), + } + } + + fn lifecycle( + &mut self, + ctx: &mut LifeCycleCtx, + event: &LifeCycle, + data: &LapceTabData, + env: &Env, + ) { + } + + fn update( + &mut self, + ctx: &mut UpdateCtx, + old_data: &LapceTabData, + data: &LapceTabData, + env: &Env, + ) { + } + + fn layout( + &mut self, + ctx: &mut LayoutCtx, + bc: &BoxConstraints, + data: &LapceTabData, + env: &Env, + ) -> Size { + bc.max() + } + + fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {} +} diff --git a/core/src/source_control.rs b/core/src/source_control.rs index 2ce76e13fc..02389249c1 100644 --- a/core/src/source_control.rs +++ b/core/src/source_control.rs @@ -17,7 +17,7 @@ use druid::{ use crate::{ command::{CommandTarget, LapceCommand, LapceUICommand, LAPCE_UI_COMMAND}, config::LapceTheme, - data::{FocusArea, LapceTabData}, + data::{FocusArea, LapceTabData, PanelKind}, editor::{LapceEditorContainer, LapceEditorView}, keypress::KeyPressFocus, movement::Movement, @@ -330,7 +330,7 @@ impl Widget for SourceControlFileList { } ctx.request_focus(); source_control.active = self.widget_id; - data.focus_area = FocusArea::SourceControl; + data.focus_area = FocusArea::Panel(PanelKind::SourceControl); ctx.set_handled(); } Event::KeyDown(key_event) => { @@ -355,7 +355,8 @@ impl Widget for SourceControlFileList { let source_control = Arc::make_mut(&mut data.source_control); source_control.active = self.widget_id; - data.focus_area = FocusArea::SourceControl; + data.focus_area = + FocusArea::Panel(PanelKind::SourceControl); ctx.request_focus(); } ctx.set_handled(); diff --git a/core/src/status.rs b/core/src/status.rs index 1aee405e64..08498bb922 100644 --- a/core/src/status.rs +++ b/core/src/status.rs @@ -3,6 +3,10 @@ use druid::piet::TextLayout; use druid::piet::TextLayoutBuilder; use druid::theme; use druid::Color; +use druid::Command; +use druid::EventCtx; +use druid::MouseEvent; +use druid::Target; use druid::Vec2; use druid::{ kurbo::Line, Event, FontDescriptor, FontFamily, Point, RenderContext, Size, @@ -10,18 +14,116 @@ use druid::{ }; use lsp_types::DiagnosticSeverity; +use crate::command::CommandTarget; +use crate::command::LapceCommandNew; +use crate::command::LapceWorkbenchCommand; +use crate::command::LAPCE_NEW_COMMAND; use crate::command::{LapceUICommand, LAPCE_UI_COMMAND}; use crate::config::LapceTheme; use crate::data::FocusArea; use crate::data::LapceTabData; +use crate::data::PanelKind; +use crate::panel::PanelPosition; use crate::state::Mode; +use crate::svg::get_svg; +use crate::tab::LapceIcon; use crate::theme::OldLapceTheme; -pub struct LapceStatusNew {} +pub struct LapceStatusNew { + height: f64, + panel_icons: Vec, + mouse_pos: Point, + icon_size: f64, +} impl LapceStatusNew { pub fn new() -> Self { - Self {} + Self { + height: 25.0, + panel_icons: Vec::new(), + mouse_pos: Point::ZERO, + icon_size: 13.0, + } + } + + fn panel_icons(&self, self_size: Size, data: &LapceTabData) -> Vec { + let left_panels = data + .panels + .get(&PanelPosition::BottomLeft) + .map(|p| { + p.widgets + .iter() + .map(|(_, kind)| kind.clone()) + .collect::>() + }) + .unwrap_or(Vec::new()); + let mut right_panels = data + .panels + .get(&PanelPosition::BottomRight) + .map(|p| { + p.widgets + .iter() + .map(|(_, kind)| kind.clone()) + .collect::>() + }) + .unwrap_or(Vec::new()); + let mut panels = left_panels; + panels.append(&mut right_panels); + + let panel_icons_size = self_size.height * panels.len() as f64; + let offset = (self_size.width - panel_icons_size) / 2.0; + + let icons: Vec = panels + .iter() + .enumerate() + .map(|(i, p)| { + let cmd = match p { + PanelKind::FileExplorer => LapceWorkbenchCommand::ToggleTerminal, + PanelKind::SourceControl => { + LapceWorkbenchCommand::ToggleSourceControl + } + PanelKind::Plugin => LapceWorkbenchCommand::TogglePlugin, + PanelKind::Terminal => LapceWorkbenchCommand::ToggleTerminal, + PanelKind::Problem => LapceWorkbenchCommand::ToggleProblem, + }; + LapceIcon { + icon: p.svg_name(), + rect: Size::new(self_size.height, self_size.height) + .to_rect() + .with_origin(Point::new( + offset + self_size.height * i as f64, + 0.0, + )), + command: Command::new( + LAPCE_NEW_COMMAND, + LapceCommandNew { + cmd: cmd.to_string(), + palette_desc: None, + target: CommandTarget::Workbench, + }, + Target::Widget(data.id), + ), + } + }) + .collect(); + icons + } + + fn icon_hit_test(&self, mouse_event: &MouseEvent) -> bool { + for icon in self.panel_icons.iter() { + if icon.rect.contains(mouse_event.pos) { + return true; + } + } + false + } + + fn mouse_down(&self, ctx: &mut EventCtx, mouse_event: &MouseEvent) { + for icon in self.panel_icons.iter() { + if icon.rect.contains(mouse_event.pos) { + ctx.submit_command(icon.command.clone()); + } + } } } @@ -33,6 +135,22 @@ impl Widget for LapceStatusNew { data: &mut LapceTabData, env: &druid::Env, ) { + match event { + Event::MouseMove(mouse_event) => { + self.mouse_pos = mouse_event.pos; + if self.icon_hit_test(mouse_event) { + ctx.set_cursor(&druid::Cursor::Pointer); + ctx.request_paint(); + } else { + ctx.clear_cursor(); + ctx.request_paint(); + } + } + Event::MouseDown(mouse_event) => { + self.mouse_down(ctx, mouse_event); + } + _ => {} + } } fn lifecycle( @@ -78,8 +196,9 @@ impl Widget for LapceStatusNew { data: &LapceTabData, env: &druid::Env, ) -> Size { - ctx.set_paint_insets((0.0, 10.0, 0.0, 0.0)); - Size::new(bc.max().width, 25.0) + let self_size = Size::new(bc.max().width, self.height); + self.panel_icons = self.panel_icons(self_size, data); + self_size } fn paint( @@ -106,15 +225,16 @@ impl Widget for LapceStatusNew { if data.config.lapce.modal { let (mode, color) = { - let mode = if data.focus_area == FocusArea::Terminal { - data.terminal - .terminals - .get(&data.terminal.active_term_id) - .unwrap() - .mode - } else { - data.main_split.active_editor().cursor.get_mode() - }; + let mode = + if data.focus_area == FocusArea::Panel(PanelKind::Terminal) { + data.terminal + .terminals + .get(&data.terminal.active_term_id) + .unwrap() + .mode + } else { + data.main_split.active_editor().cursor.get_mode() + }; match mode { Mode::Normal => ("Normal", Color::rgb8(64, 120, 242)), Mode::Insert => ("Insert", Color::rgb8(228, 86, 73)), @@ -179,5 +299,26 @@ impl Widget for LapceStatusNew { ctx.draw_text(&text_layout, Point::new(left + 10.0, 4.0)); left += 10.0 + text_layout.size().width; } + + let icon_padding = (self.height - self.icon_size) / 2.0; + for icon in self.panel_icons.iter() { + if icon.rect.contains(self.mouse_pos) { + ctx.fill( + &icon.rect, + data.config + .get_color_unchecked(LapceTheme::EDITOR_CURRENT_LINE), + ); + } + if let Some(svg) = get_svg(&icon.icon) { + ctx.draw_svg( + &svg, + icon.rect.inflate(-icon_padding, -icon_padding), + Some( + data.config + .get_color_unchecked(LapceTheme::EDITOR_FOREGROUND), + ), + ); + } + } } } diff --git a/core/src/tab.rs b/core/src/tab.rs index 1afd2074fc..805af1cdca 100644 --- a/core/src/tab.rs +++ b/core/src/tab.rs @@ -37,6 +37,7 @@ use crate::{ palette::{NewPalette, PaletteViewLens}, panel::{PanelPosition, PanelResizePosition}, plugin::Plugin, + problem::Problem, scroll::LapceScrollNew, source_control::SourceControlNew, split::LapceSplitNew, @@ -109,6 +110,9 @@ impl LapceTabNew { let terminal = TerminalPanel::new(&data); panels.insert(data.terminal.widget_id, WidgetPod::new(terminal.boxed())); + let problem = Problem::new(&data); + panels.insert(data.problem.widget_id, WidgetPod::new(problem.boxed())); + Self { id: data.id, activity: WidgetPod::new(activity), diff --git a/core/src/terminal.rs b/core/src/terminal.rs index eee93bfa4c..3efcb26274 100644 --- a/core/src/terminal.rs +++ b/core/src/terminal.rs @@ -43,7 +43,7 @@ use crate::{ LAPCE_NEW_COMMAND, LAPCE_UI_COMMAND, }, config::{Config, LapceTheme}, - data::{FocusArea, LapceTabData}, + data::{FocusArea, LapceTabData, PanelKind}, find::Find, keypress::KeyPressFocus, movement::{LinePosition, Movement}, @@ -1069,7 +1069,7 @@ impl LapceTerminal { Arc::make_mut(&mut data.terminal).active = self.widget_id; Arc::make_mut(&mut data.terminal).active_term_id = self.term_id; data.focus = self.widget_id; - data.focus_area = FocusArea::Terminal; + data.focus_area = FocusArea::Panel(PanelKind::Terminal); let terminal = data.terminal.terminals.get(&self.term_id).unwrap(); if let Some(widget_panel_id) = terminal.panel_widget_id.as_ref() { for (pos, panel) in data.panels.iter_mut() { diff --git a/icons/error.svg b/icons/error.svg new file mode 100644 index 0000000000..3c35622732 --- /dev/null +++ b/icons/error.svg @@ -0,0 +1,3 @@ + + +