From 7cb4e93d14ce58312aeea39c5f4b1bda0db628d8 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 21 Feb 2022 03:40:25 -0400 Subject: [PATCH 01/10] Improvements to the StatusView * Support for predicates by introducing add_item(). * Support for performing click actions on items. * Support for optional tooltips on item hover. * Deprecate the usage of get_item(). --- data/core/statusview.lua | 398 ++++++++++++++++++++++++++++++++++----- 1 file changed, 347 insertions(+), 51 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 1e5140f2..02b089bb 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -4,36 +4,114 @@ local command = require "core.command" local config = require "core.config" local style = require "core.style" local DocView = require "core.docview" +local CommandView = require "core.commandview" local LogView = require "core.logview" local View = require "core.view" local Object = require "core.object" +---@alias StatusView.styledtext table +---@alias StatusView.itemscb fun():StatusView.styledtext,StatusView.styledtext +---@alias StatusView.clickcb fun(button: string, x: number, y: number) +---@alias StatusView.predicate fun():boolean +---A status bar implementation for lite, check core.status_view. +---@class StatusView : View +---@field private items StatusView.item[] +---@field private hovered_item StatusView.item +---@field private message_timeout number +---@field private message StatusView.styledtext +---@field private tooltip_mode boolean +---@field private tooltip StatusView.styledtext local StatusView = View:extend() +---@class StatusView.item +---@field predicate StatusView.predicate +---@field items StatusView.itemscb +---@field onclick StatusView.clickcb +---@field tooltip string | nil +---@field lx number +---@field lw number +---@field rx number +---@field rw number +---@field active boolean +StatusView.item = {} + +---Space separator +---@type string StatusView.separator = " " + +---Pipe separator +---@type string StatusView.separator2 = " | " +---Constructor function StatusView:new() StatusView.super.new(self) self.message_timeout = 0 self.message = {} self.tooltip_mode = false self.tooltip = {} + self.items = {} + self.hovered_item = {} + self.pointer = {x = 0, y = 0} + + self:add_item( + function() + return core.active_view:is(DocView) and + not core.active_view:is(CommandView) + end, + self.get_doc_items + ) + + self:add_item("core.commandview", self.get_command_items) end -function StatusView:on_mouse_pressed() - core.set_active_view(core.last_active_view) - if system.get_time() < self.message_timeout - and not core.active_view:is(LogView) then - command.perform "core:open-log" +---Adds an item to be rendered in the status bar. +---@param predicate string | table | StatusView.predicate : +---A coindition to evaluate if the item should be displayed. If a string +---is given it is treated as a file that returns a valid object which is +---checked against the current active view, the sames applies if a table is +---given. A function can be used instead to perform a custom evaluation. +---@param itemscb StatusView.itemscb : +---This function should return two tables of StatusView.styledtext elements +---for both left and right, empty tables are allowed. +---@param pos? integer : +---The position in which to insert the given item on the internal table, +---a value of -1 inserts the item at the end which is the default. A value +---of 1 will insert the item at the beggining. +---@param onclick? StatusView.clickcb Executed when user clicks the item +---@param tooltip? string Displayed when mouse hovers the item +function StatusView:add_item(predicate, itemscb, pos, onclick, tooltip) + predicate = predicate or always_true + if type(predicate) == "string" then + predicate = require(predicate) + end + if type(predicate) == "table" then + local class = predicate + predicate = function() return core.active_view:is(class) end + end + ---@type StatusView.item + local item = { + predicate = predicate, + items = itemscb, + onclick = onclick, + tooltip = tooltip + } + pos = type(pos) == "nil" and -1 or math.abs(tonumber(pos)) + if pos == -1 then + table.insert(self.items, item) + else + table.insert(self.items, pos, item) end - return true end +---Shows a message for a predefined amount of time. +---@param icon string +---@param icon_color renderer.color +---@param text string function StatusView:show_message(icon, icon_color, text) self.message = { icon_color, style.icon_font, icon, @@ -43,30 +121,27 @@ function StatusView:show_message(icon, icon_color, text) end +---Activates tooltip mode displaying only the given +---text until StatusView:remove_tooltip() is called. +---@param text string | StatusView.styledtext function StatusView:show_tooltip(text) - self.tooltip = { text } + self.tooltip = type(text) == "table" and text or { text } self.tooltip_mode = true end +---Deactivates tooltip mode. function StatusView:remove_tooltip() self.tooltip_mode = false end -function StatusView:update() - self.size.y = style.font:get_height() + style.padding.y * 2 - - if system.get_time() < self.message_timeout then - self.scroll.to.y = self.size.y - else - self.scroll.to.y = 0 - end - - StatusView.super.update(self) -end - - +---Helper function to draw the styled text. +---@param self StatusView +---@param items StatusView.styledtext +---@param x number +---@param y number +---@param draw_fn fun(font,color,text,align, x,y,w,h):number local function draw_items(self, items, x, y, draw_fn) local font = style.font local color = style.text @@ -85,11 +160,20 @@ local function draw_items(self, items, x, y, draw_fn) end +---Helper function to calculate the width of text by using it as part of +---the helper function draw_items(). +---@param font renderer.font +---@param text string +---@param x number local function text_width(font, _, text, _, x) return x + font:get_width(text) end +---Draws a table of styled text on the status bar starting on the left or right. +---@param items StatusView.styledtext +---@param right_align boolean +---@param yoffset number function StatusView:draw_items(items, right_align, yoffset) local x, y = self:get_content_offset() y = y + (yoffset or 0) @@ -104,38 +188,78 @@ function StatusView:draw_items(items, right_align, yoffset) end -function StatusView:get_items() - if getmetatable(core.active_view) == DocView then - local dv = core.active_view - local line, col = dv.doc:get_selection() - local dirty = dv.doc:is_dirty() - local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() - local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " - local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown" +---Draw the tooltip of a given status bar item. +---@param item StatusView.item +function StatusView:draw_item_tooltip(item) + core.root_view:defer_draw(function() + local text = item.tooltip + local w = style.font:get_width(text) + local h = style.font:get_height() + local x = self.pointer.x - (w / 2) - (style.padding.x * 2) - return { - dirty and style.accent or style.text, style.icon_font, "f", - style.dim, style.font, self.separator2, style.text, - dv.doc.filename and style.text or style.dim, dv.doc:get_name(), - style.text, - self.separator, - "line: ", line, - self.separator, - col > config.line_limit and style.accent or style.text, "col: ", col, - style.text, - self.separator, - string.format("%.f%%", line / #dv.doc.lines * 100), - }, { - style.text, indent_label, indent_size, - style.dim, self.separator2, style.text, - style.icon_font, "g", - style.font, style.dim, self.separator2, style.text, - #dv.doc.lines, " lines", - self.separator, - dv.doc.crlf and "CRLF" or "LF" - } - end + if x < 0 then x = 0 end + if x + w + (style.padding.x * 2) > self.size.x then + x = self.size.x - w - (style.padding.x * 2) + end + renderer.draw_rect( + x, + self.position.y - h - (style.padding.y * 2), + w + (style.padding.x * 2), + h + (style.padding.y * 2), + style.background3 + ) + + renderer.draw_text( + style.font, + text, + x + style.padding.x, + self.position.y - h - style.padding.y, + style.text + ) + end) +end + + +---The predefined status bar items displayed when a document view is active. +---@return table left +---@return table right +function StatusView:get_doc_items() + local dv = core.active_view + local line, col = dv.doc:get_selection() + local dirty = dv.doc:is_dirty() + local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() + local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " + local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown" + + return { + dirty and style.accent or style.text, style.icon_font, "f", + style.dim, style.font, self.separator2, style.text, + dv.doc.filename and style.text or style.dim, dv.doc:get_name(), + style.text, + self.separator, + "line: ", line, + self.separator, + col > config.line_limit and style.accent or style.text, "col: ", col, + style.text, + self.separator, + string.format("%.f%%", line / #dv.doc.lines * 100), + }, { + style.text, indent_label, indent_size, + style.dim, self.separator2, style.text, + style.icon_font, "g", + style.font, style.dim, self.separator2, style.text, + #dv.doc.lines, " lines", + self.separator, + dv.doc.crlf and "CRLF" or "LF" + } +end + + +---The predefined status bar items displayed when a command view is active. +---@return table left +---@return table right +function StatusView:get_command_items() return {}, { style.icon_font, "g", style.font, style.dim, self.separator2, @@ -145,6 +269,175 @@ function StatusView:get_items() end +---Helper function to copy a styled text table into another. +---@param t1 StatusView.styledtext +---@param t2 StatusView.styledtext +local function table_add(t1, t2) + for i, value in ipairs(t2) do + table.insert(t1, value) + end +end + + +---Get the styled text that will be displayed on the left side or +---right side of the status bar checking their predicates and performing +---positioning calculations for proper functioning of tooltips and clicks. +---@return StatusView.styledtext left +---@return StatusView.styledtext right +function StatusView:get_items_from_list() + local left, right = {}, {} + + local x = self:get_content_offset() + + local rx = x + self.size.x + local lx = x + style.padding.x + local rw, lw = 0, 0 + + ---@class items + ---@field item StatusView.item + local items = {} + + -- calculate left and right width + for _, item in ipairs(self.items) do + if item.predicate(self) then + item.active = true + + local litems, ritems = item.items(self) + + if #litems > 0 then + item.lw = draw_items(self, litems, 0, 0, text_width) + item.lx = lx + lw = lw + item.lw + lx = lx + item.lw + end + + if #ritems > 0 then + item.rw = draw_items(self, ritems, 0, 0, text_width) + item.rx = rx + rw = rw + item.rw + rx = rx + item.rw + end + + if #litems > 0 or #ritems > 0 then + table.insert(items, { + item = item, + left = litems, + right = ritems + }) + end + else + item.active = false + end + end + + -- load deprecated items for compatibility + local dleft, dright = self:get_items(true) + if #dright > 0 then + rw = rw + draw_items(self, dright, 0, 0, text_width) + end + + rw = rw < (self.size.x / 2) and rw or self.size.x / 2 + + for _, item in ipairs(items) do + if #item.left > 0 then + table_add(left, item.left) + end + if #item.right > 0 then + -- re-calculate x position now that we have the total width + item.item.rx = item.item.rx - rw - style.padding.x + table_add(right, item.right) + end + end + + table_add(left, dleft) + table_add(right, dright) + + return left, right +end + + +---Older method of retrieving the status bar items and which is now +---deprecated in favour of core.status_view:add_item(). +---@deprecated +---@param nowarn boolean +---@return table left +---@return table right +function StatusView:get_items(nowarn) + if not nowarn and not self.get_items_warn then + core.error( + "Overriding StatusView:get_items() is deprecated, " + .. "use core.status_view:add_item() instead." + ) + self.get_items_warn = true + end + return {}, {} +end + + +function StatusView:on_mouse_pressed() + core.set_active_view(core.last_active_view) + if system.get_time() < self.message_timeout + and not core.active_view:is(LogView) then + command.perform "core:open-log" + end + return true +end + + +function StatusView:on_mouse_moved(x, y, dx, dy) + StatusView.super.on_mouse_moved(self, x, y, dx, dy) + + if y < self.position.y then self.hovered_item = {} return end + + for _, item in ipairs(self.items) do + if item.onclick and item.active then + if + (item.lx and x > item.lx and (item.lx + item.lw) > x) + or + (item.rx and x > item.rx and (item.rx + item.rw) > x) + then + self.pointer.x = x + self.pointer.y = y + if self.hovered_item ~= item then + self.hovered_item = item + end + return + end + end + end + self.hovered_item = {} +end + + +function StatusView:on_mouse_released(button, x, y) + StatusView.super.on_mouse_released(self, button, x, y) + + if y < self.position.y or not self.hovered_item.onclick then return end + + local item = self.hovered_item + if + (item.lx and x > item.lx and (item.lx + item.lw) > x) + or + (item.rx and x > item.rx and (item.rx + item.rw) > x) + then + self.hovered_item.onclick(button, x, y) + end +end + + +function StatusView:update() + self.size.y = style.font:get_height() + style.padding.y * 2 + + if system.get_time() < self.message_timeout then + self.scroll.to.y = self.size.y + else + self.scroll.to.y = 0 + end + + StatusView.super.update(self) +end + + function StatusView:draw() self:draw_background(style.background2) @@ -155,9 +448,12 @@ function StatusView:draw() if self.tooltip_mode then self:draw_items(self.tooltip) else - local left, right = self:get_items() + local left, right = self:get_items_from_list() self:draw_items(left) self:draw_items(right, true) + if self.hovered_item.tooltip then + self:draw_item_tooltip(self.hovered_item) + end end end From 90983b22a4770fc6bfd5e52e6dc9459d73b41f28 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 23 Feb 2022 05:15:14 -0400 Subject: [PATCH 02/10] StatusView v2.0, some changes include: * Items are now objects that can be retrieved and manipulated. * clip rect is used for left and right panes * initial support for items to do their own custom drawing by also doing a clip rect for them * a custom background color can be specified for the item. * a command or function can be executed on item click. * Introduced functionality to easily hide or show all or specific items. * Better handling of deprecated get_items() * Spacing is automatically added to items and cleaned on deprecated items. * Default items where separated and given the names: doc:file, doc:position, doc:indentation, doc:lines, doc:line-ending, core.commandview. * Some default right or left click actions where given to the default items. * Started adding required bits to support dragging to left and right panes when some items aren't visible. Note: should work well enough already but maybe some repetitive stuff can be cleaned out. --- data/core/statusview.lua | 827 +++++++++++++++++++++++++++++---------- 1 file changed, 614 insertions(+), 213 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 02b089bb..395dbe0a 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -9,33 +9,28 @@ local LogView = require "core.logview" local View = require "core.view" local Object = require "core.object" + ---@alias StatusView.styledtext table ----@alias StatusView.itemscb fun():StatusView.styledtext,StatusView.styledtext ----@alias StatusView.clickcb fun(button: string, x: number, y: number) ----@alias StatusView.predicate fun():boolean ---A status bar implementation for lite, check core.status_view. ---@class StatusView : View ----@field private items StatusView.item[] ----@field private hovered_item StatusView.item +---@field private items StatusView.Item[] +---@field private active_items StatusView.Item[] +---@field private hovered_item StatusView.Item ---@field private message_timeout number ---@field private message StatusView.styledtext ---@field private tooltip_mode boolean ---@field private tooltip StatusView.styledtext +---@field private left_width number +---@field private right_width number +---@field private r_left_width number +---@field private r_right_width number +---@field private left_xoffset number +---@field private right_xoffset number +---@field private dragged_panel '"left"' | '"right"' +---@field private hide_messages boolean local StatusView = View:extend() ----@class StatusView.item ----@field predicate StatusView.predicate ----@field items StatusView.itemscb ----@field onclick StatusView.clickcb ----@field tooltip string | nil ----@field lx number ----@field lw number ----@field rx number ----@field rw number ----@field active boolean -StatusView.item = {} - ---Space separator ---@type string StatusView.separator = " " @@ -44,6 +39,107 @@ StatusView.separator = " " ---@type string StatusView.separator2 = " | " +---@alias StatusView.Item.separator +---|>'StatusView.separator' # Space separator +---| 'StatusView.separator2' # Pipe separator + +---@alias StatusView.Item.predicate fun():boolean +---@alias StatusView.Item.onclick fun(button: string, x: number, y: number) +---@alias StatusView.Item.getitem fun():StatusView.styledtext,StatusView.styledtext +---@alias StatusView.Item.ondraw fun(x, y, h, calc_only: boolean):number + +---@class StatusView.Item : Object +---@field name string +---@field predicate StatusView.Item.predicate +---@field alignment StatusView.Item.alignment +---@field tooltip string | nil +---@field command string | nil @Command to perform when the item is clicked. +---@field on_click StatusView.Item.onclick | nil @Function called when item is clicked and no command is set. +---@field on_draw StatusView.Item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. +---@field background_color renderer.color | nil +---@field visible boolean +---@field separator StatusView.Item.separator +---@field private active boolean +---@field private x number +---@field private w number +---@field private deprecated boolean +---@field private cached_item StatusView.styledtext +StatusView.Item = Object:extend() + +---Flag to tell the item should me aligned on left side of status bar. +---@type number +StatusView.Item.LEFT = 1 + +---Flag to tell the item should me aligned on right side of status bar. +---@type number +StatusView.Item.RIGHT = 2 + +---@alias StatusView.Item.alignment +---|>'StatusView.Item.LEFT' +---| 'StatusView.Item.RIGHT' + +---Constructor +---@param predicate string | table | StatusView.Item.predicate +---@param name string +---@param alignment StatusView.Item.alignment +---@param command string | StatusView.Item.onclick +---@param tooltip? string | nil +function StatusView.Item:new(predicate, name, alignment, command, tooltip) + self:set_predicate(predicate) + self.name = name + self.alignment = alignment or StatusView.Item.LEFT + self.command = type(command) == "string" and command or nil + self.tooltip = tooltip or "" + self.on_click = type(command) == "function" and command or nil + self.on_draw = nil + self.background_color = nil + self.visible = true + self.active = false + self.x = 0 + self.w = 0 + self.separator = StatusView.separator +end + +---Called by the status bar each time that the item needs to be rendered, +---if on_draw() is set this function is obviated. +---@return StatusView.styledtext +function StatusView.Item:get_item() return {} end + +---Do not show the item on the status bar. +function StatusView.Item:hide() self.visible = false end + +---Show the item on the status bar. +function StatusView.Item:show() self.visible = true end + +---Function assiged by default when user provides a nil predicate. +local function predicate_always_true() return true end + +---A condition to evaluate if the item should be displayed. If a string +---is given it is treated as a require import that should return a valid object +---which is checked against the current active view, the sames applies if a +---table is given. A function that returns a boolean can be used instead to +---perform a custom evaluation, setting to nil means always evaluates to true. +---@param predicate string | table | StatusView.Item.predicate +function StatusView.Item:set_predicate(predicate) + predicate = predicate or predicate_always_true + if type(predicate) == "string" then + predicate = require(predicate) + end + if type(predicate) == "table" then + local class = predicate + predicate = function() return core.active_view:is(class) end + end + self.predicate = predicate +end + + +---Predicated used on the default docview widgets. +---@return boolean +local function predicate_docview() + return core.active_view:is(DocView) + and not core.active_view:is(CommandView) +end + ---Constructor function StatusView:new() @@ -53,57 +149,228 @@ function StatusView:new() self.tooltip_mode = false self.tooltip = {} self.items = {} + self.active_items = {} self.hovered_item = {} self.pointer = {x = 0, y = 0} + self.left_width = 0 + self.right_width = 0 + self.r_left_width = 0 + self.r_right_width = 0 + self.left_xoffset = 0 + self.right_xoffset = 0 + self.dragged_panel = "" + self.hide_messages = false + + self:register_docview_items() + self:register_command_items() +end + +---The predefined status bar items displayed when a document view is active. +function StatusView:register_docview_items() + if self:get_item("doc:file") then return end self:add_item( + predicate_docview, + "doc:file", + StatusView.Item.LEFT, function() - return core.active_view:is(DocView) and - not core.active_view:is(CommandView) - end, - self.get_doc_items + local dv = core.active_view + return { + dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f", + style.dim, style.font, self.separator2, style.text, + dv.doc.filename and style.text or style.dim, dv.doc:get_name() + } + end ) - self:add_item("core.commandview", self.get_command_items) + self:add_item( + predicate_docview, + "doc:position", + StatusView.Item.LEFT, + function() + local dv = core.active_view + local line, col = dv.doc:get_selection() + return { + line, + ":", + col > config.line_limit and style.accent or style.text, col, + style.text, + self.separator, + string.format("%.f%%", line / #dv.doc.lines * 100) + } + end, + "doc:go-to-line" + ).tooltip = "line : column" + + self:add_item( + predicate_docview, + "doc:indentation", + StatusView.Item.RIGHT, + function() + local dv = core.active_view + local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() + local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " + return { + style.text, indent_label, indent_size + } + end, + function(button, x, y) + if button == "left" then + command.perform "indent:set-file-indent-size" + elseif button == "right" then + command.perform "indent:set-file-indent-type" + end + end + ).separator = self.separator2 + + self:add_item( + predicate_docview, + "doc:lines", + StatusView.Item.RIGHT, + function() + local dv = core.active_view + return { + style.text, + style.icon_font, "g", + style.font, style.dim, self.separator2, style.text, + #dv.doc.lines, " lines", + } + end + ).separator = self.separator2 + + self:add_item( + predicate_docview, + "doc:line-ending", + StatusView.Item.RIGHT, + function() + local dv = core.active_view + return { + dv.doc.crlf and "CRLF" or "LF" + } + end, + "doc:toggle-line-ending" + ) +end + + +---The predefined status bar items displayed when a command view is active. +function StatusView:register_command_items() + if self:get_item("command:files") then return end + + self:add_item( + "core.commandview", + "command:files", + StatusView.Item.RIGHT, + function() + return { + style.icon_font, "g", + style.font, style.dim, self.separator2, + #core.docs, style.text, " / ", + #core.project_files, " files" + } + end + ) end ---Adds an item to be rendered in the status bar. ----@param predicate string | table | StatusView.predicate : ----A coindition to evaluate if the item should be displayed. If a string ----is given it is treated as a file that returns a valid object which is ----checked against the current active view, the sames applies if a table is ----given. A function can be used instead to perform a custom evaluation. ----@param itemscb StatusView.itemscb : ----This function should return two tables of StatusView.styledtext elements ----for both left and right, empty tables are allowed. +---@param predicate string | table | StatusView.Item.predicate : +---A condition to evaluate if the item should be displayed. If a string +---is given it is treated as a require import that should return a valid object +---which is checked against the current active view, the sames applies if a +---table is given. A function that returns a boolean can be used instead to +---perform a custom evaluation, setting to nil means always evaluates to true. +---@param name string A unique name to identify the item on the status bar. +---@param alignment StatusView.Item.alignment +---@param getitem StatusView.Item.getitem : +---A function that should return a StatusView.styledtext element, +---returning empty table is allowed. +---@param command? string | StatusView.Item.onclick : +---The name of a valid registered command or a callback function to execute +---when the item is clicked. ---@param pos? integer : ---The position in which to insert the given item on the internal table, ---a value of -1 inserts the item at the end which is the default. A value ---of 1 will insert the item at the beggining. ----@param onclick? StatusView.clickcb Executed when user clicks the item ---@param tooltip? string Displayed when mouse hovers the item -function StatusView:add_item(predicate, itemscb, pos, onclick, tooltip) - predicate = predicate or always_true - if type(predicate) == "string" then - predicate = require(predicate) - end - if type(predicate) == "table" then - local class = predicate - predicate = function() return core.active_view:is(class) end - end - ---@type StatusView.item - local item = { - predicate = predicate, - items = itemscb, - onclick = onclick, - tooltip = tooltip - } - pos = type(pos) == "nil" and -1 or math.abs(tonumber(pos)) +---@return StatusView.Item +function StatusView:add_item(predicate, name, alignment, getitem, command, pos, tooltip) + assert(self:get_item(name) == nil, "status item already exists: " .. name) + ---@type StatusView.Item + local item = StatusView.Item(predicate, name, alignment, command, tooltip) + item.get_item = getitem + pos = type(pos) == "nil" and -1 or tonumber(pos) if pos == -1 then table.insert(self.items, item) else - table.insert(self.items, pos, item) + table.insert(self.items, math.abs(pos), item) + end + return item +end + + +---Get an item associated object or nil if not found. +---@param name string +---@return StatusView.Item | nil +function StatusView:get_item(name) + for _, item in ipairs(self.items) do + if item.name == name then return item end + end + return nil +end + + +---Get a list of items. +---@param alignment? StatusView.Item.alignment +---@return StatusView.Item[] +function StatusView:get_items_list(alignment) + if alignment then + local items = {} + for _, item in ipairs(self.items) do + if item.alignment == alignment then + table.insert(items, item) + end + end + return items + end + return self.items +end + + +---Hides the given items from the status view or all if no names given. +---@param names table | string | nil +function StatusView:hide_items(names) + if type(names) == "string" then + names = {names} + end + if not names then + for _, item in ipairs(self.items) do + item:hide() + end + return + end + for _, name in ipairs(names) do + local item = self:get_item(name) + if item then item:hide() end + end +end + + +---Shows the given items from the status view or all if no names given. +---@param names table | string | nil +function StatusView:show_items(names) + if type(names) == "string" then + names = {names} + end + if not names then + for _, item in ipairs(self.items) do + item:show() + end + return + end + for _, name in ipairs(names) do + local item = self:get_item(name) + if item then item:show() end end end @@ -113,6 +380,7 @@ end ---@param icon_color renderer.color ---@param text string function StatusView:show_message(icon, icon_color, text) + if self.hide_messages then return end self.message = { icon_color, style.icon_font, icon, style.dim, style.font, StatusView.separator2, style.text, text @@ -121,6 +389,13 @@ function StatusView:show_message(icon, icon_color, text) end +---Enable or disable system wide messages on the status bar. +---@param enable boolean +function StatusView:display_messages(enable) + self.hide_messages = not enable +end + + ---Activates tooltip mode displaying only the given ---text until StatusView:remove_tooltip() is called. ---@param text string | StatusView.styledtext @@ -174,8 +449,9 @@ end ---@param items StatusView.styledtext ---@param right_align boolean ---@param yoffset number -function StatusView:draw_items(items, right_align, yoffset) +function StatusView:draw_items(items, right_align, xoffset, yoffset) local x, y = self:get_content_offset() + x = x + (xoffset or 0) y = y + (yoffset or 0) if right_align then local w = draw_items(self, items, 0, 0, text_width) @@ -189,7 +465,7 @@ end ---Draw the tooltip of a given status bar item. ----@param item StatusView.item +---@param item StatusView.Item function StatusView:draw_item_tooltip(item) core.root_view:defer_draw(function() local text = item.tooltip @@ -203,7 +479,7 @@ function StatusView:draw_item_tooltip(item) end renderer.draw_rect( - x, + x + style.padding.x, self.position.y - h - (style.padding.y * 2), w + (style.padding.x * 2), h + (style.padding.y * 2), @@ -213,7 +489,7 @@ function StatusView:draw_item_tooltip(item) renderer.draw_text( style.font, text, - x + style.padding.x, + x + (style.padding.x * 2), self.position.y - h - style.padding.y, style.text ) @@ -221,141 +497,6 @@ function StatusView:draw_item_tooltip(item) end ----The predefined status bar items displayed when a document view is active. ----@return table left ----@return table right -function StatusView:get_doc_items() - local dv = core.active_view - local line, col = dv.doc:get_selection() - local dirty = dv.doc:is_dirty() - local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() - local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " - local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown" - - return { - dirty and style.accent or style.text, style.icon_font, "f", - style.dim, style.font, self.separator2, style.text, - dv.doc.filename and style.text or style.dim, dv.doc:get_name(), - style.text, - self.separator, - "line: ", line, - self.separator, - col > config.line_limit and style.accent or style.text, "col: ", col, - style.text, - self.separator, - string.format("%.f%%", line / #dv.doc.lines * 100), - }, { - style.text, indent_label, indent_size, - style.dim, self.separator2, style.text, - style.icon_font, "g", - style.font, style.dim, self.separator2, style.text, - #dv.doc.lines, " lines", - self.separator, - dv.doc.crlf and "CRLF" or "LF" - } -end - - ----The predefined status bar items displayed when a command view is active. ----@return table left ----@return table right -function StatusView:get_command_items() - return {}, { - style.icon_font, "g", - style.font, style.dim, self.separator2, - #core.docs, style.text, " / ", - #core.project_files, " files" - } -end - - ----Helper function to copy a styled text table into another. ----@param t1 StatusView.styledtext ----@param t2 StatusView.styledtext -local function table_add(t1, t2) - for i, value in ipairs(t2) do - table.insert(t1, value) - end -end - - ----Get the styled text that will be displayed on the left side or ----right side of the status bar checking their predicates and performing ----positioning calculations for proper functioning of tooltips and clicks. ----@return StatusView.styledtext left ----@return StatusView.styledtext right -function StatusView:get_items_from_list() - local left, right = {}, {} - - local x = self:get_content_offset() - - local rx = x + self.size.x - local lx = x + style.padding.x - local rw, lw = 0, 0 - - ---@class items - ---@field item StatusView.item - local items = {} - - -- calculate left and right width - for _, item in ipairs(self.items) do - if item.predicate(self) then - item.active = true - - local litems, ritems = item.items(self) - - if #litems > 0 then - item.lw = draw_items(self, litems, 0, 0, text_width) - item.lx = lx - lw = lw + item.lw - lx = lx + item.lw - end - - if #ritems > 0 then - item.rw = draw_items(self, ritems, 0, 0, text_width) - item.rx = rx - rw = rw + item.rw - rx = rx + item.rw - end - - if #litems > 0 or #ritems > 0 then - table.insert(items, { - item = item, - left = litems, - right = ritems - }) - end - else - item.active = false - end - end - - -- load deprecated items for compatibility - local dleft, dright = self:get_items(true) - if #dright > 0 then - rw = rw + draw_items(self, dright, 0, 0, text_width) - end - - rw = rw < (self.size.x / 2) and rw or self.size.x / 2 - - for _, item in ipairs(items) do - if #item.left > 0 then - table_add(left, item.left) - end - if #item.right > 0 then - -- re-calculate x position now that we have the total width - item.item.rx = item.item.rx - rw - style.padding.x - table_add(right, item.right) - end - end - - table_add(left, dleft) - table_add(right, dright) - - return left, right -end - - ---Older method of retrieving the status bar items and which is now ---deprecated in favour of core.status_view:add_item(). ---@deprecated @@ -370,7 +511,210 @@ function StatusView:get_items(nowarn) ) self.get_items_warn = true end - return {}, {} + return {"{:dummy:}"}, {"{:dummy:}"} +end + + +---Helper function to copy a styled text table into another. +---@param t1 StatusView.styledtext +---@param t2 StatusView.styledtext +local function table_add(t1, t2) + for i, value in ipairs(t2) do + table.insert(t1, value) + end +end + + +---Helper function to merge deprecated items to a temp items table. +---@param destination table +---@param items StatusView.styledtext +---@param alignment StatusView.Item.alignment +local function merge_deprecated_items(destination, items, alignment) + local start = true + local items_start, items_end = {}, {} + for i, value in ipairs(items) do + if value ~= "{:dummy:}" then + if start then + table.insert(items_start, i, value) + else + table.insert(items_end, value) + end + else + start = false + end + end + + local position = alignment == StatusView.Item.LEFT and "left" or "right" + + local item_start = StatusView.Item( + predicate_always_true, + "deprecated:"..position.."-start", + alignment + ) + item_start.get_item = items_start + + local item_end = StatusView.Item( + predicate_always_true, + "deprecated:"..position.."-end", + alignment + ) + item_end.get_item = items_end + + table.insert(destination, 1, item_start) + table.insert(destination, item_end) +end + + +---@param self StatusView +---@param styled_text StatusView.styledtext +---@param separator string +local function add_spacing(self, styled_text, separator) + if + Object.is(styled_text[1], renderer.font) + or + ( + styled_text[2] ~= self.separator + or + styled_text[2] ~= self.separator2 + ) + then + if separator == self.separator2 then + table.insert(styled_text, 1, style.dim) + else + table.insert(styled_text, 1, style.text) + end + table.insert(styled_text, 2, separator) + end +end + + +---@param self StatusView +---@param styled_text StatusView.styledtext +local function remove_spacing(self, styled_text) + if + not Object.is(styled_text[1], renderer.font) + and + type(styled_text[1]) == "table" + and + ( + styled_text[2] == self.separator + or + styled_text[2] == self.separator2 + ) + then + table.remove(styled_text, 1) + table.remove(styled_text, 1) + end + + if + not Object.is(styled_text[#styled_text-1], renderer.font) + and + type(styled_text[#styled_text-1]) == "table" + and + ( + styled_text[#styled_text] == self.separator + or + styled_text[#styled_text] == self.separator2 + ) + then + table.remove(styled_text, #styled_text) + table.remove(styled_text, #styled_text) + end +end + + +---Get the styled text that will be displayed on the left side or +---right side of the status bar checking their predicates and performing +---positioning calculations for proper functioning of tooltips and clicks. +---@return StatusView.styledtext left +---@return StatusView.styledtext right +function StatusView:update_active_items() + local left, right = {}, {} + + local x = self:get_content_offset() + + local rx = x + self.size.x + local lx = x + local rw, lw = 0, 0 + + self.active_items = {} + + ---@type StatusView.Item[] + local combined_items = {} + table_add(combined_items, self.items) + + -- load deprecated items for compatibility + local dleft, dright = self:get_items(true) + merge_deprecated_items(combined_items, dleft, StatusView.Item.LEFT) + merge_deprecated_items(combined_items, dright, StatusView.Item.RIGHT) + + local lfirst, rfirst = true, true + + -- calculate left and right width + for _, item in ipairs(combined_items) do + item.cached_item = {} + if item.visible and item.predicate(self) then + local styled_text = type(item.get_item) == "function" + and item.get_item(self) or item.get_item + + if #styled_text > 0 then + remove_spacing(self, styled_text) + end + + if #styled_text > 0 or item.on_draw then + item.active = true + if item.alignment == StatusView.Item.LEFT then + if not lfirst then + add_spacing(self, styled_text, item.separator, true) + else + lfirst = false + end + item.w = item.on_draw and + item.on_draw(lx, self.position.y, self.size.y, true) + or + draw_items(self, styled_text, 0, 0, text_width) + item.x = lx + lw = lw + item.w + lx = lx + item.w + else + if not rfirst then + add_spacing(self, styled_text, item.separator, true) + else + rfirst = false + end + item.w = item.on_draw and + item.on_draw(rx, self.position.y, self.size.y, true) + or + draw_items(self, styled_text, 0, 0, text_width) + item.x = rx + rw = rw + item.w + rx = rx + item.w + end + item.cached_item = styled_text + table.insert(self.active_items, item) + else + item.active = false + end + else + item.active = false + end + end + + self.r_left_width, self.r_right_width = lw, rw + + if lw + rw + (style.padding.x * 2) > self.size.x then + lw = self.size.x / 2 - (style.padding.x * 2) + rw = self.size.x / 2 - (style.padding.x * 2) + end + + self.left_width, self.right_width = lw, rw + + for _, item in ipairs(self.active_items) do + if item.alignment == StatusView.Item.RIGHT then + -- re-calculate x position now that we have the total width + item.x = item.x - rw - (style.padding.x * 2) + end + end end @@ -380,6 +724,7 @@ function StatusView:on_mouse_pressed() and not core.active_view:is(LogView) then command.perform "core:open-log" end + self.mouse_pressed = true return true end @@ -387,15 +732,18 @@ end function StatusView:on_mouse_moved(x, y, dx, dy) StatusView.super.on_mouse_moved(self, x, y, dx, dy) - if y < self.position.y then self.hovered_item = {} return end + if y < self.position.y or system.get_time() <= self.message_timeout then + self.hovered_item = {} return + end for _, item in ipairs(self.items) do - if item.onclick and item.active then - if - (item.lx and x > item.lx and (item.lx + item.lw) > x) - or - (item.rx and x > item.rx and (item.rx + item.rw) > x) - then + if + item.visible and item.active + and + (item.command or item.on_click or item.tooltip ~= "") + then + local item_x = item.x + style.padding.x + if x > item_x and (item_x + item.w) > x then self.pointer.x = x self.pointer.y = y if self.hovered_item ~= item then @@ -412,15 +760,18 @@ end function StatusView:on_mouse_released(button, x, y) StatusView.super.on_mouse_released(self, button, x, y) - if y < self.position.y or not self.hovered_item.onclick then return end + self.mouse_pressed = false + + if y < self.position.y or not self.hovered_item.active then return end local item = self.hovered_item - if - (item.lx and x > item.lx and (item.lx + item.lw) > x) - or - (item.rx and x > item.rx and (item.rx + item.rw) > x) - then - self.hovered_item.onclick(button, x, y) + local item_x = item.x + style.padding.x + if x > item_x and (item_x + item.w) > x then + if item.command then + command.perform(item.command) + elseif item.on_click then + self.hovered_item.on_click(button, x, y) + end end end @@ -435,24 +786,74 @@ function StatusView:update() end StatusView.super.update(self) + + self:update_active_items() end function StatusView:draw() self:draw_background(style.background2) - if self.message then - self:draw_items(self.message, false, self.size.y) - end - - if self.tooltip_mode then + if self.message and system.get_time() <= self.message_timeout then + self:draw_items(self.message, false, 0, self.size.y) + elseif self.tooltip_mode then self:draw_items(self.tooltip) else - local left, right = self:get_items_from_list() - self:draw_items(left) - self:draw_items(right, true) - if self.hovered_item.tooltip then - self:draw_item_tooltip(self.hovered_item) + if #self.active_items > 0 then + --- draw left pane + core.push_clip_rect( + 0, self.position.y, + self.left_width + style.padding.x, self.size.y + ) + for _, item in ipairs(self.active_items) do + if item.alignment == StatusView.Item.LEFT then + if type(item.background_color) == "table" then + renderer.draw_rect( + item.x + style.padding.x, self.position.y, + item.w, self.size.y, item.background_color + ) + end + if item.on_draw then + core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) + item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.pop_clip_rect() + else + self:draw_items(item.cached_item, false, item.x) + end + end + end + core.pop_clip_rect() + + --- draw right pane + core.push_clip_rect( + self.size.x - (self.right_width + style.padding.x), self.position.y, + self.right_width + style.padding.x, self.size.y + ) + local rx = 0 + for _, item in ipairs(self.active_items) do + if item.alignment == StatusView.Item.RIGHT then + if type(item.background_color) == "table" then + renderer.draw_rect( + item.x + style.padding.x, self.position.y, + item.w, self.size.y, item.background_color + ) + end + if item.on_draw then + core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) + item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.pop_clip_rect() + else + self:draw_items(item.cached_item, false, item.x) + end + rx = rx + item.w + end + end + core.pop_clip_rect() + + -- draw tooltip + if self.hovered_item.tooltip ~= "" and self.hovered_item.active then + self:draw_item_tooltip(self.hovered_item) + end end end end From 3452d499e50c95fad2db1257fb46782d2f953b1e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 23 Feb 2022 05:56:59 -0400 Subject: [PATCH 03/10] Show hand cursor on clickable elements as suggested by @Guldoman --- data/core/statusview.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 395dbe0a..b51b0ba8 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -733,7 +733,9 @@ function StatusView:on_mouse_moved(x, y, dx, dy) StatusView.super.on_mouse_moved(self, x, y, dx, dy) if y < self.position.y or system.get_time() <= self.message_timeout then - self.hovered_item = {} return + self.cursor = "arrow" + self.hovered_item = {} + return end for _, item in ipairs(self.items) do @@ -749,10 +751,14 @@ function StatusView:on_mouse_moved(x, y, dx, dy) if self.hovered_item ~= item then self.hovered_item = item end + if item.command or item.on_click then + self.cursor = "hand" + end return end end end + self.cursor = "arrow" self.hovered_item = {} end From c5648e1f02dd6aa97ed719a9940017c539704b94 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 23 Feb 2022 19:25:25 -0400 Subject: [PATCH 04/10] StatusView: implemented dragging and scrolling. --- data/core/statusview.lua | 113 ++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index b51b0ba8..b1295ec8 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -28,6 +28,7 @@ local Object = require "core.object" ---@field private left_xoffset number ---@field private right_xoffset number ---@field private dragged_panel '"left"' | '"right"' +---@field private hovered_panel '"left"' | '"right"' ---@field private hide_messages boolean local StatusView = View:extend() @@ -159,6 +160,7 @@ function StatusView:new() self.left_xoffset = 0 self.right_xoffset = 0 self.dragged_panel = "" + self.hovered_panel = "" self.hide_messages = false self:register_docview_items() @@ -705,6 +707,9 @@ function StatusView:update_active_items() if lw + rw + (style.padding.x * 2) > self.size.x then lw = self.size.x / 2 - (style.padding.x * 2) rw = self.size.x / 2 - (style.padding.x * 2) + else + self.left_xoffset = 0 + self.right_xoffset = 0 end self.left_width, self.right_width = lw, rw @@ -718,13 +723,57 @@ function StatusView:update_active_items() end -function StatusView:on_mouse_pressed() - core.set_active_view(core.last_active_view) - if system.get_time() < self.message_timeout - and not core.active_view:is(LogView) then - command.perform "core:open-log" +---Drag the given panel if possible. +---@param panel '"left"' | '"right"' +---@param distance number +function StatusView:drag_panel(panel, dx) + if panel == "left" and self.r_left_width > self.left_width then + local nonvisible_w = self.r_left_width - self.left_width + local new_offset = self.left_xoffset + dx + if new_offset >= 0 - nonvisible_w and new_offset <= 0 then + self.left_xoffset = new_offset + end + elseif panel == "right" and self.r_right_width > self.right_width then + local nonvisible_w = self.r_right_width - self.right_width + local new_offset = self.right_xoffset + dx + if new_offset >= 0 - nonvisible_w and new_offset <= 0 then + self.right_xoffset = new_offset + end + end +end + + +---Return the currently hovered panel or empty string if none. +---@param x number +---@param y number +---@return string +function StatusView:get_hovered_panel(x, y) + if y >= self.position.y and x <= self.left_width + style.padding.x then + return "left" + else + return "right" + end + return "" +end + + +function StatusView:on_mouse_pressed(button, x, y, clicks) + core.set_active_view(core.last_active_view) + if + system.get_time() < self.message_timeout + and + not core.active_view:is(LogView) + then + command.perform "core:open-log" + else + if y >= self.position.y and button == "left" and clicks == 1 then + self.position.dx = x + if self.r_left_width > self.left_width then + self.dragged_panel = self:get_hovered_panel(x, y) + self.cursor = "hand" + end + end end - self.mouse_pressed = true return true end @@ -732,6 +781,13 @@ end function StatusView:on_mouse_moved(x, y, dx, dy) StatusView.super.on_mouse_moved(self, x, y, dx, dy) + self.hovered_panel = self:get_hovered_panel(x, y) + + if self.dragged_panel ~= "" then + self:drag_panel(self.dragged_panel, dx) + return + end + if y < self.position.y or system.get_time() <= self.message_timeout then self.cursor = "arrow" self.hovered_item = {} @@ -744,7 +800,11 @@ function StatusView:on_mouse_moved(x, y, dx, dy) and (item.command or item.on_click or item.tooltip ~= "") then - local item_x = item.x + style.padding.x + local item_ox = item.alignment == StatusView.Item.LEFT and + self.left_xoffset or self.right_xoffset + + local item_x = item_ox + item.x + style.padding.x + if x > item_x and (item_x + item.w) > x then self.pointer.x = x self.pointer.y = y @@ -766,22 +826,35 @@ end function StatusView:on_mouse_released(button, x, y) StatusView.super.on_mouse_released(self, button, x, y) - self.mouse_pressed = false + if self.dragged_panel ~= "" then + self.dragged_panel = "" + self.cursor = "arrow" + if self.position.dx ~= x then + return + end + end if y < self.position.y or not self.hovered_item.active then return end local item = self.hovered_item - local item_x = item.x + style.padding.x + local item_ox = item.alignment == StatusView.Item.LEFT and + self.left_xoffset or self.right_xoffset + local item_x = item_ox + item.x + style.padding.x if x > item_x and (item_x + item.w) > x then if item.command then command.perform(item.command) elseif item.on_click then - self.hovered_item.on_click(button, x, y) + item.on_click(button, x, y) end end end +function StatusView:on_mouse_wheel(y) + self:drag_panel(self.hovered_panel, y * self.left_width / 10) +end + + function StatusView:update() self.size.y = style.font:get_height() + style.padding.y * 2 @@ -812,19 +885,20 @@ function StatusView:draw() self.left_width + style.padding.x, self.size.y ) for _, item in ipairs(self.active_items) do + local item_x = self.left_xoffset + item.x + style.padding.x if item.alignment == StatusView.Item.LEFT then if type(item.background_color) == "table" then renderer.draw_rect( - item.x + style.padding.x, self.position.y, + item_x, self.position.y, item.w, self.size.y, item.background_color ) end if item.on_draw then - core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) - item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y) core.pop_clip_rect() else - self:draw_items(item.cached_item, false, item.x) + self:draw_items(item.cached_item, false, item_x - style.padding.x) end end end @@ -835,23 +909,22 @@ function StatusView:draw() self.size.x - (self.right_width + style.padding.x), self.position.y, self.right_width + style.padding.x, self.size.y ) - local rx = 0 for _, item in ipairs(self.active_items) do + local item_x = self.right_xoffset + item.x + style.padding.x if item.alignment == StatusView.Item.RIGHT then if type(item.background_color) == "table" then renderer.draw_rect( - item.x + style.padding.x, self.position.y, + item_x, self.position.y, item.w, self.size.y, item.background_color ) end if item.on_draw then - core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) - item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y) core.pop_clip_rect() else - self:draw_items(item.cached_item, false, item.x) + self:draw_items(item.cached_item, false, item_x - style.padding.x) end - rx = rx + item.w end end core.pop_clip_rect() From b1ce68548923ed24dd76f7f307f386d9b99a178b Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 00:21:21 -0400 Subject: [PATCH 05/10] StatusView: better calculate panel sizes and handle out of bounds drags. --- data/core/statusview.lua | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index b1295ec8..629fe35b 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -704,9 +704,20 @@ function StatusView:update_active_items() self.r_left_width, self.r_right_width = lw, rw - if lw + rw + (style.padding.x * 2) > self.size.x then - lw = self.size.x / 2 - (style.padding.x * 2) - rw = self.size.x / 2 - (style.padding.x * 2) + -- try to calc best size for left and right + if lw + rw + (style.padding.x * 4) > self.size.x then + if lw + (style.padding.x * 2) < self.size.x / 2 then + rw = self.size.x - lw - (style.padding.x * 3) + if rw > self.r_right_width then + lw = lw + (rw - self.r_right_width) + rw = self.r_right_width + end + elseif rw + (style.padding.x * 2) < self.size.x / 2 then + lw = self.size.x - rw - (style.padding.x * 3) + else + lw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) + rw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) + end else self.left_xoffset = 0 self.right_xoffset = 0 @@ -725,19 +736,27 @@ end ---Drag the given panel if possible. ---@param panel '"left"' | '"right"' ----@param distance number +---@param dx number function StatusView:drag_panel(panel, dx) if panel == "left" and self.r_left_width > self.left_width then local nonvisible_w = self.r_left_width - self.left_width local new_offset = self.left_xoffset + dx if new_offset >= 0 - nonvisible_w and new_offset <= 0 then self.left_xoffset = new_offset + elseif dx < 0 then + self.left_xoffset = 0 - nonvisible_w + else + self.left_xoffset = 0 end elseif panel == "right" and self.r_right_width > self.right_width then local nonvisible_w = self.r_right_width - self.right_width local new_offset = self.right_xoffset + dx if new_offset >= 0 - nonvisible_w and new_offset <= 0 then self.right_xoffset = new_offset + elseif dx < 0 then + self.right_xoffset = 0 - nonvisible_w + else + self.right_xoffset = 0 end end end From 131bdf9fb2bf8aec92b95fd44dbcb96b401ebc03 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 02:23:29 -0400 Subject: [PATCH 06/10] StatusView: implemented and applied get_item_visible_area. --- data/core/statusview.lua | 49 ++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 629fe35b..cb9673a1 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -776,6 +776,41 @@ function StatusView:get_hovered_panel(x, y) end +---@param item StatusView.Item +---@return number x +---@return number w +function StatusView:get_item_visible_area(item) + local item_ox = item.alignment == StatusView.Item.LEFT and + self.left_xoffset or self.right_xoffset + + local item_x = item_ox + item.x + style.padding.x + local item_w = item.w + + if item.alignment == StatusView.Item.LEFT then + if self.left_width - item_x > 0 and self.left_width - item_x < item.w then + item_w = (self.left_width + style.padding.x) - item_x + elseif self.left_width - item_x < 0 then + item_x = 0 + item_w = 0 + end + else + local rx = self.size.x - self.right_width - style.padding.x + if item_x < rx then + if item_x + item.w > rx then + item_x = rx + item_w = (item_x + item.w) - rx + else + item_x = 0 + item_w = 0 + end + end + end + + return item_x, item_w +end + + + function StatusView:on_mouse_pressed(button, x, y, clicks) core.set_active_view(core.last_active_view) if @@ -819,12 +854,9 @@ function StatusView:on_mouse_moved(x, y, dx, dy) and (item.command or item.on_click or item.tooltip ~= "") then - local item_ox = item.alignment == StatusView.Item.LEFT and - self.left_xoffset or self.right_xoffset + local item_x, item_w = self:get_item_visible_area(item) - local item_x = item_ox + item.x + style.padding.x - - if x > item_x and (item_x + item.w) > x then + if x > item_x and (item_x + item_w) > x then self.pointer.x = x self.pointer.y = y if self.hovered_item ~= item then @@ -856,10 +888,9 @@ function StatusView:on_mouse_released(button, x, y) if y < self.position.y or not self.hovered_item.active then return end local item = self.hovered_item - local item_ox = item.alignment == StatusView.Item.LEFT and - self.left_xoffset or self.right_xoffset - local item_x = item_ox + item.x + style.padding.x - if x > item_x and (item_x + item.w) > x then + local item_x, item_w = self:get_item_visible_area(item) + + if x > item_x and (item_x + item_w) > x then if item.command then command.perform(item.command) elseif item.on_click then From 128e10ba4733fa1affc15dd5428dbe04eef4c3e4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 02:57:39 -0400 Subject: [PATCH 07/10] StatusView: add spaces as separate items instead of pre-pending them to items. --- data/core/statusview.lua | 74 ++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index cb9673a1..0f793be6 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -63,7 +63,6 @@ StatusView.separator2 = " | " ---@field private active boolean ---@field private x number ---@field private w number ----@field private deprecated boolean ---@field private cached_item StatusView.styledtext StatusView.Item = Object:extend() @@ -193,8 +192,7 @@ function StatusView:register_docview_items() local dv = core.active_view local line, col = dv.doc:get_selection() return { - line, - ":", + style.text, line, ":", col > config.line_limit and style.accent or style.text, col, style.text, self.separator, @@ -234,8 +232,8 @@ function StatusView:register_docview_items() return { style.text, style.icon_font, "g", - style.font, style.dim, self.separator2, style.text, - #dv.doc.lines, " lines", + style.font, style.dim, self.separator2, + style.text, #dv.doc.lines, " lines", } end ).separator = self.separator2 @@ -247,7 +245,7 @@ function StatusView:register_docview_items() function() local dv = core.active_view return { - dv.doc.crlf and "CRLF" or "LF" + style.text, dv.doc.crlf and "CRLF" or "LF" } end, "doc:toggle-line-ending" @@ -267,7 +265,7 @@ function StatusView:register_command_items() return { style.icon_font, "g", style.font, style.dim, self.separator2, - #core.docs, style.text, " / ", + style.text, #core.docs, style.text, " / ", #core.project_files, " files" } end @@ -311,7 +309,7 @@ function StatusView:add_item(predicate, name, alignment, getitem, command, pos, end ----Get an item associated object or nil if not found. +---Get an item object associated to a name or nil if not found. ---@param name string ---@return StatusView.Item | nil function StatusView:get_item(name) @@ -450,7 +448,8 @@ end ---Draws a table of styled text on the status bar starting on the left or right. ---@param items StatusView.styledtext ---@param right_align boolean ----@param yoffset number +---@param xoffset? number +---@param yoffset? number function StatusView:draw_items(items, right_align, xoffset, yoffset) local x, y = self:get_content_offset() x = x + (xoffset or 0) @@ -567,29 +566,30 @@ local function merge_deprecated_items(destination, items, alignment) end +---Append a space item into the given items list. ---@param self StatusView ----@param styled_text StatusView.styledtext +---@param destination StatusView.Item[] ---@param separator string -local function add_spacing(self, styled_text, separator) - if - Object.is(styled_text[1], renderer.font) - or - ( - styled_text[2] ~= self.separator - or - styled_text[2] ~= self.separator2 - ) - then - if separator == self.separator2 then - table.insert(styled_text, 1, style.dim) - else - table.insert(styled_text, 1, style.text) - end - table.insert(styled_text, 2, separator) - end +---@param alignment StatusView.Item.alignment +---@return StatusView.Item +local function add_spacing(self, destination, separator, alignment, x) + ---@type StatusView.Item + local space = StatusView.Item(nil, "space", alignment) + space.cached_item = separator == self.separator and { + style.text, separator + } or { + style.dim, separator + } + space.x = x + space.w = draw_items(self, space.cached_item, 0, 0, text_width) + + table.insert(destination, space) + + return space end +---Remove starting and ending separators. ---@param self StatusView ---@param styled_text StatusView.styledtext local function remove_spacing(self, styled_text) @@ -625,11 +625,9 @@ local function remove_spacing(self, styled_text) end ----Get the styled text that will be displayed on the left side or ----right side of the status bar checking their predicates and performing ----positioning calculations for proper functioning of tooltips and clicks. ----@return StatusView.styledtext left ----@return StatusView.styledtext right +---Set the active items that will be displayed on the left or right side +---of the status bar checking their predicates and performing positioning +---calculations for proper functioning of tooltips and clicks. function StatusView:update_active_items() local left, right = {}, {} @@ -667,7 +665,11 @@ function StatusView:update_active_items() item.active = true if item.alignment == StatusView.Item.LEFT then if not lfirst then - add_spacing(self, styled_text, item.separator, true) + local space = add_spacing( + self, self.active_items, item.separator, item.alignment, lx + ) + lw = lw + space.w + lx = lx + space.w else lfirst = false end @@ -680,7 +682,11 @@ function StatusView:update_active_items() lx = lx + item.w else if not rfirst then - add_spacing(self, styled_text, item.separator, true) + local space = add_spacing( + self, self.active_items, item.separator, item.alignment, rx + ) + rw = rw + space.w + rx = rx + space.w else rfirst = false end From 15a31418cad88503754de65e4257420604aae9d3 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 14:22:52 -0400 Subject: [PATCH 08/10] StatusView: restored indent confirmed --- data/core/statusview.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 0f793be6..c40c8924 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -211,7 +211,8 @@ function StatusView:register_docview_items() local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " return { - style.text, indent_label, indent_size + style.text, indent_label, indent_size, + indent_confirmed and "" or "*" } end, function(button, x, y) From 4c80aa7a40c04ae4f6ed726e713adf9ca78c2a7e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Feb 2022 17:04:37 -0400 Subject: [PATCH 09/10] StatusView: reposition left and right panel offsets on window resize. --- data/core/statusview.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index c40c8924..fa7352f6 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -725,6 +725,17 @@ function StatusView:update_active_items() lw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) rw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) end + -- reposition left and right offsets when window is resized + if rw >= self.r_right_width then + self.right_xoffset = 0 + elseif rw > self.right_xoffset + self.r_right_width then + self.right_xoffset = rw - self.r_right_width + end + if lw >= self.r_left_width then + self.left_xoffset = 0 + elseif lw > self.left_xoffset + self.r_left_width then + self.left_xoffset = lw - self.r_left_width + end else self.left_xoffset = 0 self.right_xoffset = 0 From e9775ced78d534e1af7ec0f8401da6fbbcadde8d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Feb 2022 17:56:17 -0400 Subject: [PATCH 10/10] StatusView: added items hovered option for bg color and param to on_draw as suggested by @Guldoman --- data/core/statusview.lua | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index fa7352f6..85ea6a2b 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -47,7 +47,7 @@ StatusView.separator2 = " | " ---@alias StatusView.Item.predicate fun():boolean ---@alias StatusView.Item.onclick fun(button: string, x: number, y: number) ---@alias StatusView.Item.getitem fun():StatusView.styledtext,StatusView.styledtext ----@alias StatusView.Item.ondraw fun(x, y, h, calc_only: boolean):number +---@alias StatusView.Item.ondraw fun(x, y, h, hovered: boolean, calc_only: boolean):number ---@class StatusView.Item : Object ---@field name string @@ -58,6 +58,7 @@ StatusView.separator2 = " | " ---@field on_click StatusView.Item.onclick | nil @Function called when item is clicked and no command is set. ---@field on_draw StatusView.Item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. ---@field background_color renderer.color | nil +---@field background_color_hover renderer.color | nil ---@field visible boolean ---@field separator StatusView.Item.separator ---@field private active boolean @@ -93,6 +94,7 @@ function StatusView.Item:new(predicate, name, alignment, command, tooltip) self.on_click = type(command) == "function" and command or nil self.on_draw = nil self.background_color = nil + self.background_color_hover = nil self.visible = true self.active = false self.x = 0 @@ -664,6 +666,7 @@ function StatusView:update_active_items() if #styled_text > 0 or item.on_draw then item.active = true + local hovered = self.hovered_item == item if item.alignment == StatusView.Item.LEFT then if not lfirst then local space = add_spacing( @@ -675,7 +678,7 @@ function StatusView:update_active_items() lfirst = false end item.w = item.on_draw and - item.on_draw(lx, self.position.y, self.size.y, true) + item.on_draw(lx, self.position.y, self.size.y, hovered, true) or draw_items(self, styled_text, 0, 0, text_width) item.x = lx @@ -692,7 +695,7 @@ function StatusView:update_active_items() rfirst = false end item.w = item.on_draw and - item.on_draw(rx, self.position.y, self.size.y, true) + item.on_draw(rx, self.position.y, self.size.y, hovered, true) or draw_items(self, styled_text, 0, 0, text_width) item.x = rx @@ -938,6 +941,21 @@ function StatusView:update() end +---Retrieve the hover status and proper background color if any. +---@param self StatusView +---@param item StatusView.Item +---@return boolean is_hovered +---@return renderer.color | nil color +local function get_item_bg_color(self, item) + local hovered = self.hovered_item == item + + local item_bg = hovered + and item.background_color_hover or item.background_color + + return hovered, item_bg +end + + function StatusView:draw() self:draw_background(style.background2) @@ -954,16 +972,17 @@ function StatusView:draw() ) for _, item in ipairs(self.active_items) do local item_x = self.left_xoffset + item.x + style.padding.x + local hovered, item_bg = get_item_bg_color(self, item) if item.alignment == StatusView.Item.LEFT then - if type(item.background_color) == "table" then + if type(item_bg) == "table" then renderer.draw_rect( item_x, self.position.y, - item.w, self.size.y, item.background_color + item.w, self.size.y, item_bg ) end if item.on_draw then core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) - item.on_draw(item_x, self.position.y, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y, hovered) core.pop_clip_rect() else self:draw_items(item.cached_item, false, item_x - style.padding.x) @@ -979,16 +998,17 @@ function StatusView:draw() ) for _, item in ipairs(self.active_items) do local item_x = self.right_xoffset + item.x + style.padding.x + local hovered, item_bg = get_item_bg_color(self, item) if item.alignment == StatusView.Item.RIGHT then - if type(item.background_color) == "table" then + if type(item_bg) == "table" then renderer.draw_rect( item_x, self.position.y, - item.w, self.size.y, item.background_color + item.w, self.size.y, item_bg ) end if item.on_draw then core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) - item.on_draw(item_x, self.position.y, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y, hovered) core.pop_clip_rect() else self:draw_items(item.cached_item, false, item_x - style.padding.x)