add panels
This commit is contained in:
@@ -37,6 +37,20 @@ print("[lua] init.lua выполнен")
|
|||||||
-- app:minimize()
|
-- app:minimize()
|
||||||
-- app:is_maximized()
|
-- app:is_maximized()
|
||||||
--
|
--
|
||||||
|
-- Панели окна (по умолчанию закрыты, размер меняется перетаскиванием):
|
||||||
|
-- app.left_panel:open()
|
||||||
|
-- app.left_panel:close()
|
||||||
|
-- app.left_panel:toggle() -- вернуть true, если панель открыта
|
||||||
|
-- app.left_panel:is_open()
|
||||||
|
-- app.right_panel:open() -- те же методы, что у левой панели
|
||||||
|
-- app.right_panel:close()
|
||||||
|
-- app.right_panel:toggle()
|
||||||
|
-- app.right_panel:is_open()
|
||||||
|
-- app.bottom_panel:open() -- те же методы, что у левой панели
|
||||||
|
-- app.bottom_panel:close()
|
||||||
|
-- app.bottom_panel:toggle()
|
||||||
|
-- app.bottom_panel:is_open()
|
||||||
|
--
|
||||||
-- Методы добавляются в src/api.rs: всё, что есть в impl AppApi, помеченном
|
-- Методы добавляются в src/api.rs: всё, что есть в impl AppApi, помеченном
|
||||||
-- #[mlua::userdata_impl], автоматически доступно из Lua.
|
-- #[mlua::userdata_impl], автоматически доступно из Lua.
|
||||||
--
|
--
|
||||||
@@ -55,3 +69,6 @@ app:next_tab()
|
|||||||
-- app:map_key("<!Esc>", function() app:close() end)
|
-- app:map_key("<!Esc>", function() app:close() end)
|
||||||
|
|
||||||
app:map_key("<C-q>", function() app:close() end)
|
app:map_key("<C-q>", function() app:close() end)
|
||||||
|
|
||||||
|
app:map_key("<C-e>", function() app.right_panel:toggle() end)
|
||||||
|
app:map_key("<C-t>", function() app.bottom_panel:toggle() end)
|
||||||
|
|||||||
+108
-1
@@ -12,8 +12,10 @@ use std::cell::{Cell, RefCell};
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::constants::{BOTTOM_PANEL_HEIGHT, LEFT_PANEL_WIDTH, RIGHT_PANEL_WIDTH};
|
||||||
use crate::editor::{Language, Workspace};
|
use crate::editor::{Language, Workspace};
|
||||||
use crate::keymap::{KeyBinding, KeyMap};
|
use crate::keymap::{KeyBinding, KeyMap};
|
||||||
|
use crate::panel::{PanelHandle, PanelState};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
/// Текущий экземпляр API. `thread_local`, потому что UI GPUI привязан к
|
/// Текущий экземпляр API. `thread_local`, потому что UI GPUI привязан к
|
||||||
@@ -78,6 +80,57 @@ pub struct AppApi {
|
|||||||
workspace: Rc<RefCell<Workspace>>,
|
workspace: Rc<RefCell<Workspace>>,
|
||||||
#[lua(skip)]
|
#[lua(skip)]
|
||||||
keymap: Rc<RefCell<KeyMap>>,
|
keymap: Rc<RefCell<KeyMap>>,
|
||||||
|
#[lua(skip)]
|
||||||
|
left_panel: PanelHandle,
|
||||||
|
#[lua(skip)]
|
||||||
|
right_panel: PanelHandle,
|
||||||
|
#[lua(skip)]
|
||||||
|
bottom_panel: PanelHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// API одной панели окна.
|
||||||
|
///
|
||||||
|
/// Один и тот же тип используется для `app.left_panel`, `app.right_panel` и
|
||||||
|
/// `app.bottom_panel`: отличается только состояние, на которое ссылается
|
||||||
|
/// объект. Поэтому панели управляются одинаково: `app.left_panel:open()`,
|
||||||
|
/// `app.bottom_panel:toggle()` и так далее.
|
||||||
|
#[derive(mlua::UserData)]
|
||||||
|
pub struct PanelApi {
|
||||||
|
#[lua(skip)]
|
||||||
|
state: PanelHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanelApi {
|
||||||
|
fn new(state: PanelHandle) -> Self {
|
||||||
|
Self { state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[mlua::userdata_impl]
|
||||||
|
impl PanelApi {
|
||||||
|
/// Открывает панель.
|
||||||
|
#[lua(infallible)]
|
||||||
|
pub fn open(&self) {
|
||||||
|
self.state.borrow_mut().open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Закрывает панель.
|
||||||
|
#[lua(infallible)]
|
||||||
|
pub fn close(&self) {
|
||||||
|
self.state.borrow_mut().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Переключает панель и возвращает новое состояние.
|
||||||
|
#[lua(infallible)]
|
||||||
|
pub fn toggle(&self) -> bool {
|
||||||
|
self.state.borrow_mut().toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Открыта ли панель прямо сейчас.
|
||||||
|
#[lua(infallible)]
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.state.borrow().is_open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[mlua::userdata_impl]
|
#[mlua::userdata_impl]
|
||||||
@@ -86,7 +139,13 @@ impl AppApi {
|
|||||||
/// [`AppApi::global`].
|
/// [`AppApi::global`].
|
||||||
#[lua(skip)]
|
#[lua(skip)]
|
||||||
pub fn install(workspace: Rc<RefCell<Workspace>>, keymap: Rc<RefCell<KeyMap>>) -> Self {
|
pub fn install(workspace: Rc<RefCell<Workspace>>, keymap: Rc<RefCell<KeyMap>>) -> Self {
|
||||||
let api = Self { workspace, keymap };
|
let api = Self {
|
||||||
|
workspace,
|
||||||
|
keymap,
|
||||||
|
left_panel: Rc::new(RefCell::new(PanelState::new(LEFT_PANEL_WIDTH))),
|
||||||
|
right_panel: Rc::new(RefCell::new(PanelState::new(RIGHT_PANEL_WIDTH))),
|
||||||
|
bottom_panel: Rc::new(RefCell::new(PanelState::new(BOTTOM_PANEL_HEIGHT))),
|
||||||
|
};
|
||||||
INSTANCE.with(|slot| *slot.borrow_mut() = Some(api.clone()));
|
INSTANCE.with(|slot| *slot.borrow_mut() = Some(api.clone()));
|
||||||
api
|
api
|
||||||
}
|
}
|
||||||
@@ -104,6 +163,24 @@ impl AppApi {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Состояние левой панели (для вида).
|
||||||
|
#[lua(skip)]
|
||||||
|
pub fn left_panel_handle(&self) -> PanelHandle {
|
||||||
|
self.left_panel.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Состояние правой панели (для вида).
|
||||||
|
#[lua(skip)]
|
||||||
|
pub fn right_panel_handle(&self) -> PanelHandle {
|
||||||
|
self.right_panel.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Состояние нижней панели (для вида).
|
||||||
|
#[lua(skip)]
|
||||||
|
pub fn bottom_panel_handle(&self) -> PanelHandle {
|
||||||
|
self.bottom_panel.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Все открытые вкладки в порядке слева направо.
|
/// Все открытые вкладки в порядке слева направо.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
// Часть публичного API приложения: вызывается внешним кодом и тестами.
|
// Часть публичного API приложения: вызывается внешним кодом и тестами.
|
||||||
@@ -287,6 +364,36 @@ impl AppApi {
|
|||||||
self.workspace.borrow_mut().clear_language(index)
|
self.workspace.borrow_mut().clear_language(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Левая панель окна.
|
||||||
|
///
|
||||||
|
/// В Lua доступна как `app.left_panel` с методами `open`, `close`,
|
||||||
|
/// `toggle` и `is_open`.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[lua(getter, infallible)]
|
||||||
|
pub fn left_panel(&self) -> PanelApi {
|
||||||
|
PanelApi::new(self.left_panel.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Правая панель окна.
|
||||||
|
///
|
||||||
|
/// В Lua доступна как `app.right_panel` с методами `open`, `close`,
|
||||||
|
/// `toggle` и `is_open`.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[lua(getter, infallible)]
|
||||||
|
pub fn right_panel(&self) -> PanelApi {
|
||||||
|
PanelApi::new(self.right_panel.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Нижняя панель окна.
|
||||||
|
///
|
||||||
|
/// В Lua доступна как `app.bottom_panel` с теми же методами, что и
|
||||||
|
/// `app.right_panel`.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[lua(getter, infallible)]
|
||||||
|
pub fn bottom_panel(&self) -> PanelApi {
|
||||||
|
PanelApi::new(self.bottom_panel.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Закрывает окно приложения.
|
/// Закрывает окно приложения.
|
||||||
#[lua(infallible)]
|
#[lua(infallible)]
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
|
|||||||
+112
-4
@@ -10,16 +10,37 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||||
use gpui_kit::component::input::{Editor, EditorState, InputEvent, TabSize};
|
use gpui_kit::component::input::{Editor, EditorState, InputEvent, TabSize};
|
||||||
|
use gpui_kit::component::resizable::HandleEdge;
|
||||||
use gpui_kit::component::tab::{Tab, TabBar};
|
use gpui_kit::component::tab::{Tab, TabBar};
|
||||||
use gpui_kit::component::{ActiveTheme, IconName, Sizable, StyledExt, TitleBar, h_flex};
|
use gpui_kit::component::{
|
||||||
|
ActiveTheme, IconName, Sizable, StyledExt, TitleBar, h_flex, h_resizable, resizable_panel,
|
||||||
|
resize_handle, v_resizable,
|
||||||
|
};
|
||||||
use gpui_kit::*;
|
use gpui_kit::*;
|
||||||
|
|
||||||
use crate::api::{self, AppApi, WindowCommand};
|
use crate::api::{self, AppApi, WindowCommand};
|
||||||
use crate::constants::{TAB_SIZE, WINDOW_TITLE};
|
use crate::constants::{MIN_LEFT_PANEL_WIDTH, MIN_MAIN_AREA_WIDTH, TAB_SIZE, WINDOW_TITLE};
|
||||||
use crate::editor::Workspace;
|
use crate::editor::Workspace;
|
||||||
use crate::keymap::KeyMap;
|
use crate::keymap::KeyMap;
|
||||||
|
use crate::panel::PanelHandle;
|
||||||
use crate::script::ScriptEngine;
|
use crate::script::ScriptEngine;
|
||||||
|
|
||||||
|
/// Payload перетаскивания разделителя левой панели.
|
||||||
|
///
|
||||||
|
/// Тип payload-а нужен `on_drag_move`, чтобы отличать этот drag от
|
||||||
|
/// перетаскивания других разделителей.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct LeftPanelDrag;
|
||||||
|
|
||||||
|
/// Пустой предпросмотр: при перетаскивании виден только разделитель.
|
||||||
|
struct PanelDragPreview;
|
||||||
|
|
||||||
|
impl Render for PanelDragPreview {
|
||||||
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Приложение текстового редактора.
|
/// Приложение текстового редактора.
|
||||||
pub struct MyApp {
|
pub struct MyApp {
|
||||||
/// Открытые документы, вкладки и активная вкладка.
|
/// Открытые документы, вкладки и активная вкладка.
|
||||||
@@ -32,6 +53,12 @@ pub struct MyApp {
|
|||||||
/// Лежит перед `scripts`, чтобы обработчики (ссылки на Lua-значения)
|
/// Лежит перед `scripts`, чтобы обработчики (ссылки на Lua-значения)
|
||||||
/// уничтожились раньше самого Lua-движка.
|
/// уничтожились раньше самого Lua-движка.
|
||||||
keymap: Rc<RefCell<KeyMap>>,
|
keymap: Rc<RefCell<KeyMap>>,
|
||||||
|
/// Состояние левой панели, общее с `app.left_panel` в Lua.
|
||||||
|
left_panel: PanelHandle,
|
||||||
|
/// Состояние правой панели, общее с `app.right_panel` в Lua.
|
||||||
|
right_panel: PanelHandle,
|
||||||
|
/// Состояние нижней панели, общее с `app.bottom_panel` в Lua.
|
||||||
|
bottom_panel: PanelHandle,
|
||||||
/// Виджет редактора, показывающий активный документ.
|
/// Виджет редактора, показывающий активный документ.
|
||||||
editor: Entity<EditorState>,
|
editor: Entity<EditorState>,
|
||||||
/// Ревизия [`Workspace`], уже перенесённая в `editor`.
|
/// Ревизия [`Workspace`], уже перенесённая в `editor`.
|
||||||
@@ -97,11 +124,17 @@ impl MyApp {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let displayed_id = workspace.borrow().active().id;
|
let displayed_id = workspace.borrow().active().id;
|
||||||
AppApi::install(workspace.clone(), keymap.clone());
|
let api = AppApi::install(workspace.clone(), keymap.clone());
|
||||||
|
let left_panel = api.left_panel_handle();
|
||||||
|
let right_panel = api.right_panel_handle();
|
||||||
|
let bottom_panel = api.bottom_panel_handle();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
workspace,
|
workspace,
|
||||||
keymap,
|
keymap,
|
||||||
|
left_panel,
|
||||||
|
right_panel,
|
||||||
|
bottom_panel,
|
||||||
editor,
|
editor,
|
||||||
synced_revision: 0,
|
synced_revision: 0,
|
||||||
displayed_id,
|
displayed_id,
|
||||||
@@ -190,6 +223,81 @@ impl MyApp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Редактор с левой, правой и нижней панелями.
|
||||||
|
///
|
||||||
|
/// Все панели меняют размер перетаскиванием разделителя, а закрытые
|
||||||
|
/// панели не занимают места. Сами панели пока пустые.
|
||||||
|
fn render_workspace(&self, window: &Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let (left_open, left_size) = {
|
||||||
|
let panel = self.left_panel.borrow();
|
||||||
|
(panel.is_open(), panel.size())
|
||||||
|
};
|
||||||
|
// Левая панель не входит в ResizablePanelGroup и не сжимается вместе
|
||||||
|
// с окном, поэтому её ширину ограничиваем сами.
|
||||||
|
let max_left =
|
||||||
|
(window.bounds().size.width - px(MIN_MAIN_AREA_WIDTH)).max(px(MIN_LEFT_PANEL_WIDTH));
|
||||||
|
let left_width = px(left_size).min(max_left);
|
||||||
|
let (right_open, right_size) = {
|
||||||
|
let panel = self.right_panel.borrow();
|
||||||
|
(panel.is_open(), panel.size())
|
||||||
|
};
|
||||||
|
let (bottom_open, bottom_size) = {
|
||||||
|
let panel = self.bottom_panel.borrow();
|
||||||
|
(panel.is_open(), panel.size())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Нижняя панель занимает ширину редактора, а правая тянется на всю
|
||||||
|
// высоту — как в большинстве редакторов кода.
|
||||||
|
let editor_area = v_resizable("editor-area")
|
||||||
|
.child(resizable_panel().child(Editor::new(&self.editor).bordered(false).size_full()))
|
||||||
|
.child(
|
||||||
|
resizable_panel()
|
||||||
|
.size(px(bottom_size))
|
||||||
|
.visible(bottom_open)
|
||||||
|
.child(div().size_full().bg(cx.theme().sidebar)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let area = h_resizable("main-area")
|
||||||
|
.child(resizable_panel().child(editor_area))
|
||||||
|
.child(
|
||||||
|
resizable_panel()
|
||||||
|
.size(px(right_size))
|
||||||
|
.visible(right_open)
|
||||||
|
.child(div().size_full().bg(cx.theme().sidebar)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut row = div().size_full().flex().flex_row();
|
||||||
|
if left_open {
|
||||||
|
row = row.child(
|
||||||
|
div()
|
||||||
|
.w(left_width)
|
||||||
|
.h_full()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.relative()
|
||||||
|
.bg(cx.theme().sidebar)
|
||||||
|
.child(
|
||||||
|
resize_handle("left-panel-handle", Axis::Horizontal)
|
||||||
|
.inside(HandleEdge::Trailing)
|
||||||
|
.on_drag(LeftPanelDrag, |_, _, _, cx| cx.new(|_| PanelDragPreview)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.child(div().flex_1().min_w_0().h_full().child(area))
|
||||||
|
.on_drag_move::<LeftPanelDrag>({
|
||||||
|
let entity = cx.entity();
|
||||||
|
move |event: &DragMoveEvent<LeftPanelDrag>, _window, cx| {
|
||||||
|
let min = px(MIN_LEFT_PANEL_WIDTH);
|
||||||
|
let max = (event.bounds.size.width - px(MIN_MAIN_AREA_WIDTH)).max(min);
|
||||||
|
let width = (event.event.position.x - event.bounds.left()).clamp(min, max);
|
||||||
|
entity.update(cx, |this, cx| {
|
||||||
|
this.left_panel.borrow_mut().set_size(width.as_f32());
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Полоса вкладок: активная вкладка, маркер несохранённых изменений и «+».
|
/// Полоса вкладок: активная вкладка, маркер несохранённых изменений и «+».
|
||||||
fn render_tabs(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_tabs(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let (tabs, active) = {
|
let (tabs, active) = {
|
||||||
@@ -343,7 +451,7 @@ impl Render for MyApp {
|
|||||||
.flex_1()
|
.flex_1()
|
||||||
.w_full()
|
.w_full()
|
||||||
.min_h_0()
|
.min_h_0()
|
||||||
.child(Editor::new(&self.editor).bordered(false).size_full()),
|
.child(self.render_workspace(window, cx)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,21 @@ pub const WINDOW_TITLE: &str = "Custom Window App";
|
|||||||
/// Ширина одного уровня отступа в редакторе.
|
/// Ширина одного уровня отступа в редакторе.
|
||||||
pub const TAB_SIZE: usize = 4;
|
pub const TAB_SIZE: usize = 4;
|
||||||
|
|
||||||
|
/// Ширина правой панели при открытии.
|
||||||
|
pub const RIGHT_PANEL_WIDTH: f32 = 240.0;
|
||||||
|
|
||||||
|
/// Ширина левой панели при открытии.
|
||||||
|
pub const LEFT_PANEL_WIDTH: f32 = 220.0;
|
||||||
|
|
||||||
|
/// Минимальная ширина левой панели при перетаскивании разделителя.
|
||||||
|
pub const MIN_LEFT_PANEL_WIDTH: f32 = 120.0;
|
||||||
|
|
||||||
|
/// Минимальная ширина области редактора при изменении левой панели.
|
||||||
|
pub const MIN_MAIN_AREA_WIDTH: f32 = 240.0;
|
||||||
|
|
||||||
|
/// Высота нижней панели при открытии.
|
||||||
|
pub const BOTTOM_PANEL_HEIGHT: f32 = 160.0;
|
||||||
|
|
||||||
/// Каталог, в котором приложение ищет Lua-сценарии.
|
/// Каталог, в котором приложение ищет Lua-сценарии.
|
||||||
///
|
///
|
||||||
/// По умолчанию это текущий рабочий каталог процесса — именно там приложение
|
/// По умолчанию это текущий рабочий каталог процесса — именно там приложение
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mod app;
|
|||||||
mod constants;
|
mod constants;
|
||||||
mod editor;
|
mod editor;
|
||||||
mod keymap;
|
mod keymap;
|
||||||
|
mod panel;
|
||||||
mod script;
|
mod script;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//! Состояние боковых панелей окна.
|
||||||
|
//!
|
||||||
|
//! Модель общая с API: Lua-сценарии меняют [`PanelState`] через `app.right_panel`
|
||||||
|
//! и `app.bottom_panel`, а вид [`MyApp`](crate::app::MyApp) читает его при
|
||||||
|
//! отрисовке. Как и документы, состояние лежит в `Rc<RefCell<_>>`, чтобы вид и
|
||||||
|
//! API видели одни и те же панели.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Общая ссылка на состояние панели.
|
||||||
|
pub type PanelHandle = Rc<RefCell<PanelState>>;
|
||||||
|
|
||||||
|
/// Открыта ли панель и каким размером она раскрывается.
|
||||||
|
///
|
||||||
|
/// Размер — начальный: после перетаскивания разделителя виджет
|
||||||
|
/// `ResizablePanelGroup` запоминает фактический размер панели сам.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PanelState {
|
||||||
|
visible: bool,
|
||||||
|
size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanelState {
|
||||||
|
/// Создаёт закрытую панель с начальным размером `size` пикселей.
|
||||||
|
pub fn new(size: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
visible: false,
|
||||||
|
size: size.max(0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Открыта ли панель сейчас.
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Начальный размер панели в пикселях.
|
||||||
|
pub fn size(&self) -> f32 {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Запоминает новый размер панели в пикселях.
|
||||||
|
pub fn set_size(&mut self, size: f32) {
|
||||||
|
self.size = size.max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Открывает панель.
|
||||||
|
pub fn open(&mut self) {
|
||||||
|
self.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Закрывает панель.
|
||||||
|
pub fn close(&mut self) {
|
||||||
|
self.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Переключает видимость и возвращает новое состояние.
|
||||||
|
pub fn toggle(&mut self) -> bool {
|
||||||
|
self.visible = !self.visible;
|
||||||
|
self.visible
|
||||||
|
}
|
||||||
|
}
|
||||||
+105
@@ -184,6 +184,90 @@ fn window_commands_are_queued_for_the_view() {
|
|||||||
assert!(drain_window_commands().is_empty());
|
assert!(drain_window_commands().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panel_api_opens_closes_and_toggles_the_same_panel_state() {
|
||||||
|
let workspace = Rc::new(RefCell::new(Workspace::default()));
|
||||||
|
let (api, _keymap) = install_api(workspace);
|
||||||
|
let right = api.right_panel_handle();
|
||||||
|
let bottom = api.bottom_panel_handle();
|
||||||
|
let left = api.left_panel_handle();
|
||||||
|
|
||||||
|
assert!(!left.borrow().is_open());
|
||||||
|
assert!(!right.borrow().is_open());
|
||||||
|
assert!(!bottom.borrow().is_open());
|
||||||
|
|
||||||
|
api.left_panel().open();
|
||||||
|
assert!(left.borrow().is_open());
|
||||||
|
assert!(api.left_panel().is_open());
|
||||||
|
api.left_panel().close();
|
||||||
|
assert!(!left.borrow().is_open());
|
||||||
|
|
||||||
|
api.right_panel().open();
|
||||||
|
assert!(right.borrow().is_open());
|
||||||
|
assert!(api.right_panel().is_open());
|
||||||
|
|
||||||
|
// toggle возвращает новое состояние и не трогает вторую панель.
|
||||||
|
assert!(!api.right_panel().toggle());
|
||||||
|
assert!(!right.borrow().is_open());
|
||||||
|
assert!(api.right_panel().toggle());
|
||||||
|
assert!(right.borrow().is_open());
|
||||||
|
|
||||||
|
api.right_panel().close();
|
||||||
|
assert!(!right.borrow().is_open());
|
||||||
|
|
||||||
|
api.bottom_panel().open();
|
||||||
|
assert!(bottom.borrow().is_open());
|
||||||
|
assert!(!right.borrow().is_open());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lua_script_can_control_panels() {
|
||||||
|
let workspace = Rc::new(RefCell::new(Workspace::default()));
|
||||||
|
let (api, _keymap) = install_api(workspace);
|
||||||
|
let right = api.right_panel_handle();
|
||||||
|
let bottom = api.bottom_panel_handle();
|
||||||
|
let left = api.left_panel_handle();
|
||||||
|
|
||||||
|
let dir = temp_script_dir("panels");
|
||||||
|
let script = dir.join("panels.lua");
|
||||||
|
std::fs::write(
|
||||||
|
&script,
|
||||||
|
r#"
|
||||||
|
app.left_panel:open()
|
||||||
|
assert(app.left_panel:is_open())
|
||||||
|
app.left_panel:close()
|
||||||
|
assert(not app.left_panel:is_open())
|
||||||
|
assert(app.left_panel:toggle() == true)
|
||||||
|
|
||||||
|
app.right_panel:open()
|
||||||
|
assert(app.right_panel:is_open())
|
||||||
|
assert(not app.bottom_panel:is_open())
|
||||||
|
|
||||||
|
app.bottom_panel:open()
|
||||||
|
app.right_panel:toggle()
|
||||||
|
assert(not app.right_panel:is_open())
|
||||||
|
|
||||||
|
assert(app.bottom_panel:toggle() == false)
|
||||||
|
app.bottom_panel:close()
|
||||||
|
assert(not app.bottom_panel:is_open())
|
||||||
|
|
||||||
|
assert(app.right_panel:toggle() == true)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("panels.lua");
|
||||||
|
|
||||||
|
let engine = ScriptEngine::new(api).expect("движок Lua");
|
||||||
|
engine
|
||||||
|
.run_file(&script)
|
||||||
|
.expect("панели должны управляться из Lua");
|
||||||
|
|
||||||
|
assert!(left.borrow().is_open());
|
||||||
|
assert!(right.borrow().is_open());
|
||||||
|
assert!(!bottom.borrow().is_open());
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
/// Временный каталог с уникальным именем для Lua-сценариев.
|
/// Временный каталог с уникальным именем для Lua-сценариев.
|
||||||
fn temp_script_dir(prefix: &str) -> std::path::PathBuf {
|
fn temp_script_dir(prefix: &str) -> std::path::PathBuf {
|
||||||
let dir = std::env::temp_dir().join(format!(
|
let dir = std::env::temp_dir().join(format!(
|
||||||
@@ -461,6 +545,27 @@ fn lua_key_bindings_receive_keys_while_editor_is_focused(cx: &mut TestAppContext
|
|||||||
let _ = std::fs::remove_dir_all(&dir);
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui_kit::test]
|
||||||
|
fn opened_panels_render_without_panicking(cx: &mut TestAppContext) {
|
||||||
|
cx.update(gpui_kit::init);
|
||||||
|
|
||||||
|
let (app, visual) = cx.add_window_view(|window, cx| MyApp::new(window, cx));
|
||||||
|
|
||||||
|
let api = AppApi::global();
|
||||||
|
api.left_panel().open();
|
||||||
|
api.right_panel().open();
|
||||||
|
api.bottom_panel().open();
|
||||||
|
visual.update(|_window, cx| {
|
||||||
|
app.update(cx, |_, cx| cx.notify());
|
||||||
|
});
|
||||||
|
visual.run_until_parked();
|
||||||
|
|
||||||
|
// Состояние осталось у панелей, которые видит вид.
|
||||||
|
assert!(api.left_panel_handle().borrow().is_open());
|
||||||
|
assert!(api.right_panel_handle().borrow().is_open());
|
||||||
|
assert!(api.bottom_panel_handle().borrow().is_open());
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui_kit::test]
|
#[gpui_kit::test]
|
||||||
fn typing_into_editor_updates_active_document(cx: &mut TestAppContext) {
|
fn typing_into_editor_updates_active_document(cx: &mut TestAppContext) {
|
||||||
cx.update(gpui_kit::init);
|
cx.update(gpui_kit::init);
|
||||||
|
|||||||
Reference in New Issue
Block a user