Checkpoint: Basic tab bar structure

This commit is contained in:
Marshall Bowers 2023-11-02 14:34:48 -04:00
parent 5a41eed120
commit 8b1b7a2f80
3 changed files with 467 additions and 286 deletions

View file

@ -1,7 +1,7 @@
// mod dragged_item_receiver; // mod dragged_item_receiver;
use crate::{ use crate::{
item::{Item, ItemHandle, WeakItemHandle}, item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
toolbar::Toolbar, toolbar::Toolbar,
workspace_settings::{AutosaveSetting, WorkspaceSettings}, workspace_settings::{AutosaveSetting, WorkspaceSettings},
SplitDirection, Workspace, SplitDirection, Workspace,
@ -9,8 +9,8 @@ use crate::{
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque}; use collections::{HashMap, HashSet, VecDeque};
use gpui2::{ use gpui2::{
AppContext, AsyncWindowContext, EntityId, EventEmitter, Model, PromptLevel, Task, View, AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, Model, PromptLevel,
ViewContext, VisualContext, WeakView, WindowContext, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project2::{Project, ProjectEntryId, ProjectPath}; use project2::{Project, ProjectEntryId, ProjectPath};
@ -25,6 +25,8 @@ use std::{
Arc, Arc,
}, },
}; };
use ui::{prelude::*, Icon, IconButton, IconColor, IconElement};
use ui::{v_stack};
use util::truncate_and_remove_front; use util::truncate_and_remove_front;
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
@ -1345,6 +1347,162 @@ impl Pane {
}); });
} }
fn render_tab(
&self,
ix: usize,
item: &Box<dyn ItemHandle>,
detail: usize,
cx: &mut ViewContext<'_, Pane>,
) -> impl Component<Self> {
let label = item.tab_content(Some(detail), cx);
// let label = match (self.git_status, is_deleted) {
// (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone())
// .color(LabelColor::Hidden)
// .set_strikethrough(true),
// (GitStatus::None, false) => Label::new(self.title.clone()),
// (GitStatus::Created, false) => {
// Label::new(self.title.clone()).color(LabelColor::Created)
// }
// (GitStatus::Modified, false) => {
// Label::new(self.title.clone()).color(LabelColor::Modified)
// }
// (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent),
// (GitStatus::Conflict, false) => Label::new(self.title.clone()),
// };
let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted);
let (tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
false => (
cx.theme().colors().tab_inactive,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
true => (
cx.theme().colors().tab_active,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
};
let close_right = ItemSettings::get_global(cx).close_position.right();
div()
.id(item.id())
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
// .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
.px_2()
.py_0p5()
.flex()
.items_center()
.justify_center()
.bg(tab_bg)
.hover(|h| h.bg(tab_hover_bg))
.active(|a| a.bg(tab_active_bg))
.child(
div()
.px_1()
.flex()
.items_center()
.gap_1p5()
.children(if item.has_conflict(cx) {
Some(
IconElement::new(Icon::ExclamationTriangle)
.size(ui::IconSize::Small)
.color(IconColor::Warning),
)
} else if item.is_dirty(cx) {
Some(
IconElement::new(Icon::ExclamationTriangle)
.size(ui::IconSize::Small)
.color(IconColor::Info),
)
} else {
None
})
.children(if !close_right {
Some(close_icon())
} else {
None
})
.child(label)
.children(if close_right {
Some(close_icon())
} else {
None
}),
)
}
fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl Component<Self> {
div()
.group("tab_bar")
.id("tab_bar")
.w_full()
.flex()
.bg(cx.theme().colors().tab_bar)
// Left Side
.child(
div()
.relative()
.px_1()
.flex()
.flex_none()
.gap_2()
// Nav Buttons
.child(
div()
.right_0()
.flex()
.items_center()
.gap_px()
.child(IconButton::new("navigate_backward", Icon::ArrowLeft).state(
InteractionState::Enabled.if_enabled(self.can_navigate_backward()),
))
.child(IconButton::new("navigate_forward", Icon::ArrowRight).state(
InteractionState::Enabled.if_enabled(self.can_navigate_forward()),
)),
),
)
.child(
div().w_0().flex_1().h_full().child(
div().id("tabs").flex().overflow_x_scroll().children(
self.items
.iter()
.enumerate()
.zip(self.tab_details(cx))
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
),
),
)
// Right Side
.child(
div()
// We only use absolute here since we don't
// have opacity or `hidden()` yet
.absolute()
.neg_top_7()
.px_1()
.flex()
.flex_none()
.gap_2()
.group_hover("tab_bar", |this| this.top_0())
// Nav Buttons
.child(
div()
.flex()
.items_center()
.gap_px()
.child(IconButton::new("plus", Icon::Plus))
.child(IconButton::new("split", Icon::Split)),
),
)
}
// fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> { // fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
// let theme = theme::current(cx).clone(); // let theme = theme::current(cx).clone();
@ -1500,42 +1658,42 @@ impl Pane {
// row // row
// } // }
// fn tab_details(&self, cx: &AppContext) -> Vec<usize> { fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
// let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>(); let mut tab_details = self.items.iter().map(|_| 0).collect::<Vec<_>>();
// let mut tab_descriptions = HashMap::default(); let mut tab_descriptions = HashMap::default();
// let mut done = false; let mut done = false;
// while !done { while !done {
// done = true; done = true;
// // Store item indices by their tab description. // Store item indices by their tab description.
// for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
// if let Some(description) = item.tab_description(*detail, cx) { if let Some(description) = item.tab_description(*detail, cx) {
// if *detail == 0 if *detail == 0
// || Some(&description) != item.tab_description(detail - 1, cx).as_ref() || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
// { {
// tab_descriptions tab_descriptions
// .entry(description) .entry(description)
// .or_insert(Vec::new()) .or_insert(Vec::new())
// .push(ix); .push(ix);
// } }
// } }
// } }
// // If two or more items have the same tab description, increase their level // If two or more items have the same tab description, increase eir level
// // of detail and try again. // of detail and try again.
// for (_, item_ixs) in tab_descriptions.drain() { for (_, item_ixs) in tab_descriptions.drain() {
// if item_ixs.len() > 1 { if item_ixs.len() > 1 {
// done = false; done = false;
// for ix in item_ixs { for ix in item_ixs {
// tab_details[ix] += 1; tab_details[ix] += 1;
// } }
// } }
// } }
// } }
// tab_details tab_details
// } }
// fn render_tab( // fn render_tab(
// item: &Box<dyn ItemHandle>, // item: &Box<dyn ItemHandle>,
@ -1737,12 +1895,18 @@ impl Pane {
// type Event = Event; // type Event = Event;
// } // }
// impl View for Pane { impl Render for Pane {
type Element = Div<Self>;
// fn ui_name() -> &'static str { // fn ui_name() -> &'static str {
// "Pane" // "Pane"
// } // }
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
v_stack()
.child(self.render_tab_bar(cx))
.child(div() /* toolbar */)
.child(div() /* active item */)
// enum MouseNavigationHandler {} // enum MouseNavigationHandler {}
// MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| { // MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
@ -1920,7 +2084,7 @@ impl Pane {
// } // }
// }) // })
// .into_any_named("pane") // .into_any_named("pane")
// } }
// fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) { // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
// if !self.has_focus { // if !self.has_focus {
@ -1967,7 +2131,7 @@ impl Pane {
// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
// Self::reset_to_default_keymap_context(keymap); // Self::reset_to_default_keymap_context(keymap);
// } // }
// } }
impl ItemNavHistory { impl ItemNavHistory {
pub fn push<D: 'static + Send + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) { pub fn push<D: 'static + Send + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
@ -2747,3 +2911,16 @@ fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
// }) // })
// } // }
// } // }
#[derive(Clone, Debug)]
struct DraggedTab {
title: String,
}
impl Render for DraggedTab {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div().w_8().h_4().bg(gpui2::red())
}
}

View file

@ -6,12 +6,13 @@ use db2::sqlez::{
bindable::{Bind, Column, StaticColumnCount}, bindable::{Bind, Column, StaticColumnCount},
statement::Statement, statement::Statement,
}; };
use gpui2::{point, size, AnyElement, AnyView, Bounds, Model, Pixels, Point, View, ViewContext}; use gpui2::{
point, size, AnyElement, AnyView, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext,
};
use parking_lot::Mutex; use parking_lot::Mutex;
use project2::Project; use project2::Project;
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use theme2::ThemeVariant;
use ui::prelude::*; use ui::prelude::*;
const HANDLE_HITBOX_SIZE: f32 = 4.0; const HANDLE_HITBOX_SIZE: f32 = 4.0;
@ -128,10 +129,10 @@ impl PaneGroup {
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>, active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> { ) -> impl Component<Workspace> {
self.root.render( self.root.render(
project, project,
0, 0,
@ -189,36 +190,38 @@ impl Member {
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>, active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> { ) -> impl Component<Workspace> {
match self { match self {
Member::Pane(pane) => { Member::Pane(pane) => {
let pane_element = if Some(&**pane) == zoomed { // todo!()
None // let pane_element = if Some(pane.into()) == zoomed {
} else { // None
Some(pane) // } else {
}; // Some(pane)
// };
div().child(pane.clone()).render()
// Stack::new() // Stack::new()
// .with_child(pane_element.contained().with_border(leader_border)) // .with_child(pane_element.contained().with_border(leader_border))
// .with_children(leader_status_box) // .with_children(leader_status_box)
// .into_any() // .into_any()
let el = div() // let el = div()
.flex() // .flex()
.flex_1() // .flex_1()
.gap_px() // .gap_px()
.w_full() // .w_full()
.h_full() // .h_full()
.bg(cx.theme().colors().editor) // .bg(cx.theme().colors().editor)
.children(); // .children();
} }
Member::Axis(axis) => axis.render( Member::Axis(axis) => axis.render(
project, project,
basis + 1, basis + 1,
theme,
follower_states, follower_states,
active_call, active_call,
active_pane, active_pane,
@ -541,11 +544,10 @@ impl PaneAxis {
&self, &self,
project: &Model<Project>, project: &Model<Project>,
basis: usize, basis: usize,
theme: &ThemeVariant,
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>, active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> { ) -> AnyElement<Workspace> {

View file

@ -28,10 +28,11 @@ use futures::{
Future, FutureExt, StreamExt, Future, FutureExt, StreamExt,
}; };
use gpui2::{ use gpui2::{
div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
Component, Div, Element, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, AsyncWindowContext, Bounds, Component, Div, EntityId, EventEmitter, GlobalPixels,
ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription, Task, View, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, Styled,
ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
WindowHandle, WindowOptions,
}; };
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use language2::LanguageRegistry; use language2::LanguageRegistry;
@ -544,7 +545,7 @@ pub enum Event {
pub struct Workspace { pub struct Workspace {
weak_self: WeakView<Self>, weak_self: WeakView<Self>,
// modal: Option<ActiveModal>, // modal: Option<ActiveModal>,
// zoomed: Option<AnyWeakViewHandle>, zoomed: Option<AnyWeakView>,
// zoomed_position: Option<DockPosition>, // zoomed_position: Option<DockPosition>,
center: PaneGroup, center: PaneGroup,
left_dock: View<Dock>, left_dock: View<Dock>,
@ -622,7 +623,7 @@ impl Workspace {
} }
project2::Event::Closed => { project2::Event::Closed => {
cx.remove_window(); // cx.remove_window();
} }
project2::Event::DeletedEntry(entry_id) => { project2::Event::DeletedEntry(entry_id) => {
@ -763,7 +764,7 @@ impl Workspace {
Workspace { Workspace {
weak_self: weak_handle.clone(), weak_self: weak_handle.clone(),
// modal: None, // modal: None,
// zoomed: None, zoomed: None,
// zoomed_position: None, // zoomed_position: None,
center: PaneGroup::new(center_pane.clone()), center: PaneGroup::new(center_pane.clone()),
panes: vec![center_pane.clone()], panes: vec![center_pane.clone()],
@ -2698,7 +2699,8 @@ impl Workspace {
.id("titlebar") .id("titlebar")
.on_click(|workspace, event, cx| { .on_click(|workspace, event, cx| {
if event.up.click_count == 2 { if event.up.click_count == 2 {
cx.zoom_window(); // todo!()
// cx.zoom_window();
} }
}) })
.child("Collab title bar Item") // self.titlebar_item .child("Collab title bar Item") // self.titlebar_item
@ -3805,12 +3807,12 @@ impl Render for Workspace {
.child( .child(
div().flex().flex_col().flex_1().h_full().child( div().flex().flex_col().flex_1().h_full().child(
div().flex().flex_1().child(self.center.render( div().flex().flex_1().child(self.center.render(
project, &self.project,
follower_states, &self.follower_states,
active_call, self.active_call(),
active_pane, &self.active_pane,
zoomed, self.zoomed.as_ref(),
app_state, &self.app_state,
cx, cx,
)), )),
), // .children( ), // .children(