feat!: redesign tabs (#2745)

This commit is contained in:
三咲雅 · Misaki Masa 2025-05-11 12:11:29 +08:00 committed by GitHub
parent 41cba40072
commit ea90b0477e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 113 additions and 42 deletions

View file

@ -38,11 +38,6 @@ marker_cut = { fg = "lightred", bg = "lightred" }
marker_marked = { fg = "lightcyan", bg = "lightcyan" }
marker_selected = { fg = "lightyellow", bg = "lightyellow" }
# Tab
tab_active = { reversed = true }
tab_inactive = {}
tab_width = 1
# Count
count_copied = { fg = "white", bg = "green" }
count_cut = { fg = "white", bg = "red" }
@ -58,6 +53,19 @@ syntect_theme = ""
# : }}}
# : Tabs {{{
[tabs]
active = { bg = "blue", bold = true }
inactive = { fg = "blue", bg = "gray" }
# Separator
sep_inner = { open = "", close = "" }
sep_outer = { open = "", close = "" }
# : }}}
# : Mode {{{
[mode]

View file

@ -38,11 +38,6 @@ marker_cut = { fg = "lightred", bg = "lightred" }
marker_marked = { fg = "lightcyan", bg = "lightcyan" }
marker_selected = { fg = "lightyellow", bg = "lightyellow" }
# Tab
tab_active = { reversed = true }
tab_inactive = {}
tab_width = 1
# Count
count_copied = { fg = "white", bg = "green" }
count_cut = { fg = "white", bg = "red" }
@ -58,6 +53,19 @@ syntect_theme = ""
# : }}}
# : Tabs {{{
[tabs]
active = { bg = "blue", bold = true }
inactive = { fg = "blue", bg = "gray" }
# Separator
sep_inner = { open = "", close = "" }
sep_outer = { open = "", close = "" }
# : }}}
# : Mode {{{
[mode]

View file

@ -13,6 +13,7 @@ pub struct Theme {
pub flavor: Flavor,
#[serde(rename = "manager")]
pub mgr: Mgr, // TODO: Remove `serde(rename)`
pub tabs: Tabs,
pub mode: Mode,
pub status: Status,
pub which: Which,
@ -53,11 +54,6 @@ pub struct Mgr {
marker_marked: Style,
marker_selected: Style,
// Tab
tab_active: Style,
tab_inactive: Style,
tab_width: u8,
// Count
count_copied: Style,
count_cut: Style,
@ -71,6 +67,21 @@ pub struct Mgr {
pub syntect_theme: PathBuf,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Tabs {
pub active: Style,
pub inactive: Style,
pub sep_inner: TabsSep,
pub sep_outer: TabsSep,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct TabsSep {
pub open: String,
pub close: String,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Mode {
pub normal_main: Style,
@ -197,9 +208,7 @@ pub struct Help {
impl Theme {
pub(crate) fn reshape(mut self, light: bool) -> Result<Self> {
if self.mgr.tab_width < 1 {
bail!("[mgr].tab_width must be greater than 0");
} else if self.which.cols < 1 || self.which.cols > 3 {
if self.which.cols < 1 || self.which.cols > 3 {
bail!("[which].cols must be between 1 and 3");
}

View file

@ -9,7 +9,6 @@ Header = {
},
_right = {
{ "count", id = 1, order = 1000 },
{ "tabs", id = 2, order = 2000 },
},
}
@ -74,27 +73,6 @@ function Header:count()
}
end
function Header:tabs()
local tabs = #cx.tabs
if tabs == 1 then
return ""
end
local spans = {}
for i = 1, tabs do
local text = i
if th.mgr.tab_width > 2 then
text = ya.truncate(text .. " " .. cx.tabs[i].name, { max = th.mgr.tab_width })
end
if i == cx.tabs.idx then
spans[#spans + 1] = ui.Span(" " .. text .. " "):style(th.mgr.tab_active)
else
spans[#spans + 1] = ui.Span(" " .. text .. " "):style(th.mgr.tab_inactive)
end
end
return ui.Line(spans)
end
function Header:reflow() return { self } end
function Header:redraw()

View file

@ -15,6 +15,7 @@ function Root:layout()
:direction(ui.Layout.VERTICAL)
:constraints({
ui.Constraint.Length(1),
ui.Constraint.Length(#cx.tabs > 1 and 1 or 0),
ui.Constraint.Fill(1),
ui.Constraint.Length(1),
})
@ -24,8 +25,9 @@ end
function Root:build()
self._children = {
Header:new(self._chunks[1], cx.active),
Tab:new(self._chunks[2], cx.active),
Status:new(self._chunks[3], cx.active),
Tabs:new(self._chunks[2]),
Tab:new(self._chunks[3], cx.active),
Status:new(self._chunks[4], cx.active),
Modal:new(self._area),
}
end

View file

@ -0,0 +1,64 @@
Tabs = {
_id = "tabs",
_offsets = {},
}
function Tabs:new(area)
return setmetatable({
_area = area,
}, { __index = self })
end
function Tabs:reflow() return { self } end
function Tabs:redraw()
local len = #cx.tabs
if len < 2 then
return {}
end
local lines = {
ui.Line(th.tabs.sep_outer.open):fg(th.tabs.inactive.bg),
}
local pos = lines[1]:width()
local max = math.floor(self:inner_width() / len)
for i = 1, len do
local name = ya.truncate(string.format(" %d %s ", i, cx.tabs[i].name), { max = max })
if i == cx.tabs.idx then
lines[#lines + 1] = ui.Line {
ui.Span(th.tabs.sep_inner.open):style(th.tabs.inactive),
ui.Span(name):style(th.tabs.active),
ui.Span(th.tabs.sep_inner.close):style(th.tabs.inactive),
}
else
lines[#lines + 1] = ui.Line(name):style(th.tabs.inactive)
end
self._offsets[i], pos = pos, pos + lines[#lines]:width()
end
lines[#lines + 1] = ui.Line(th.tabs.sep_outer.close):fg(th.tabs.inactive.bg)
return { ui.Line(lines):area(self._area) }
end
function Tabs:inner_width()
local si, so = th.tabs.sep_inner, th.tabs.sep_outer
return math.max(0, self._area.w - ui.Line({ si.open, si.close, so.open, so.close }):width())
end
-- Mouse events
function Tabs:click(event, up)
if up or event.is_middle then
return
end
for i = #self._offsets, 1, -1 do
if event.x >= self._offsets[i] then
ya.emit("tab_switch", { i - 1 })
break
end
end
end
function Tabs:scroll(event, step) end
function Tabs:touch(event, step) end

View file

@ -11,6 +11,7 @@ impl Theme {
Composer::make(lua, 15, |lua, key| {
match key {
b"mgr" => lua.to_value_with(&THEME.mgr, OPTS)?,
b"tabs" => lua.to_value_with(&THEME.tabs, OPTS)?,
b"mode" => lua.to_value_with(&THEME.mode, OPTS)?,
b"status" => lua.to_value_with(&THEME.status, OPTS)?,
b"which" => lua.to_value_with(&THEME.which, OPTS)?,

View file

@ -52,6 +52,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> {
lua.load(preset!("components/root")).set_name("root.lua").exec()?;
lua.load(preset!("components/status")).set_name("status.lua").exec()?;
lua.load(preset!("components/tab")).set_name("tab.lua").exec()?;
lua.load(preset!("components/tabs")).set_name("tabs.lua").exec()?;
Ok(())
}