From 63b9cf223e752c94ef0f5abea96597406cf084bf Mon Sep 17 00:00:00 2001 From: Takase <20792268+takase1121@users.noreply.github.com> Date: Sun, 4 Apr 2021 22:11:47 +0800 Subject: [PATCH] NagView improvements (#136) Implement keyboard commands for the NagView. --- data/core/init.lua | 4 +- data/core/keymap.lua | 10 +-- data/core/nagview.lua | 203 ++++++++++++++++++++++++++++-------------- 3 files changed, 143 insertions(+), 74 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 16a09c31..d1118a45 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -481,8 +481,8 @@ function core.confirm_close_all(close_fn, ...) end local args = {...} local opt = { - { font = style.font, text = "Yes" }, - { font = style.font, text = "No" } + { font = style.font, text = "Yes", default_yes = true }, + { font = style.font, text = "No" , default_no = true } } core.nag_view:show("Unsaved Changes", text, opt, function(item) if item.text == "Yes" then close_fn(table.unpack(args)) end diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 27c59dff..6a7b70c8 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -131,7 +131,7 @@ keymap.add { ["ctrl+v"] = "doc:paste", ["ctrl+insert"] = "doc:copy", ["shift+insert"] = "doc:paste", - ["escape"] = { "command:escape", "doc:select-none" }, + ["escape"] = { "command:escape", "doc:select-none", "dialog:select-no" }, ["tab"] = { "command:complete", "doc:indent" }, ["shift+tab"] = "doc:unindent", ["backspace"] = "doc:backspace", @@ -142,8 +142,8 @@ keymap.add { ["shift+delete"] = "doc:delete", ["ctrl+delete"] = "doc:delete-to-next-word-end", ["ctrl+shift+delete"] = "doc:delete-to-next-word-end", - ["return"] = { "command:submit", "doc:newline" }, - ["keypad enter"] = { "command:submit", "doc:newline" }, + ["return"] = { "command:submit", "doc:newline", "dialog:select" }, + ["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" }, ["ctrl+return"] = "doc:newline-below", ["ctrl+shift+return"] = "doc:newline-above", ["ctrl+j"] = "doc:join-lines", @@ -156,8 +156,8 @@ keymap.add { ["ctrl+shift+d"] = "doc:duplicate-lines", ["ctrl+shift+k"] = "doc:delete-lines", - ["left"] = "doc:move-to-previous-char", - ["right"] = "doc:move-to-next-char", + ["left"] = { "doc:move-to-previous-char", "dialog:previous-entry" }, + ["right"] = { "doc:move-to-next-char", "dialog:next-entry"}, ["up"] = { "command:select-previous", "doc:move-to-previous-line" }, ["down"] = { "command:select-next", "doc:move-to-next-line" }, ["ctrl+left"] = "doc:move-to-previous-word-start", diff --git a/data/core/nagview.lua b/data/core/nagview.lua index 69dd50f0..ff5bad10 100644 --- a/data/core/nagview.lua +++ b/data/core/nagview.lua @@ -1,11 +1,12 @@ local core = require "core" -local config = require "core.config" +local command = require "core.command" local common = require "core.common" local View = require "core.view" local style = require "core.style" -local BORDER_WIDTH = common.round(2 * SCALE) -local BORDER_PADDING = common.round(5 * SCALE) +local BORDER_WIDTH = common.round(1 * SCALE) +local UNDERLINE_WIDTH = common.round(2 * SCALE) +local UNDERLINE_MARGIN = common.round(1 * SCALE) local noop = function() end @@ -15,25 +16,14 @@ function NagView:new() NagView.super.new(self) self.size.y = 0 self.force_focus = false - self.title = "Warning" - self.message = "" - self.options = {} - self.submit = noop + self.queue = {} end function NagView:get_title() return self.title end -function NagView:each_option() - return coroutine.wrap(function() - for i = #self.options, 1, -1 do - coroutine.yield(i, self.options[i]) - end - end) -end - -function NagView:get_options_height() +function NagView:get_options_line_height() local max = 0 for _, opt in ipairs(self.options) do local lh = style.font:get_height(opt.text) @@ -43,15 +33,18 @@ function NagView:get_options_height() end function NagView:get_line_height() - local maxlh = math.max(style.font:get_height(self.message), self:get_options_height()) - return 2 * BORDER_WIDTH + 2 * BORDER_PADDING + maxlh + 2 * style.padding.y + return self.max_lh + 2 * BORDER_WIDTH + 2 * style.padding.y end function NagView:update() NagView.super.update(self) - local dest = core.active_view == self and self:get_line_height() or 0 - self:move_towards(self.size, "y", dest) + if core.active_view == self and self.title then + self:move_towards(self.size, "y", self:get_line_height()) + self:move_towards(self, "underline_progress", 1) + else + self:move_towards(self.size, "y", 0) + end end function NagView:draw_overlay() @@ -63,87 +56,163 @@ function NagView:draw_overlay() end) end -function NagView:each_visible_option() - return coroutine.wrap(function() - local halfh = math.floor(self.size.y / 2) - local ox, oy = self:get_content_offset() - ox = ox + self.size.x - style.padding.x +function NagView:change_hovered(i) + if i ~= self.hovered_item then + self.hovered_item = i + self.underline_progress = 0 + core.redraw = true + end +end + +function NagView:each_option() + return coroutine.wrap(function() + if not self.options then return end + local opt, bw,bh,ox,oy + bh = self.max_lh + 2 * BORDER_WIDTH + style.padding.y + ox,oy = self:get_content_offset() + ox = ox + self.size.x + oy = oy + (self.size.y / 2) - (bh / 2) + + for i = #self.options, 1, -1 do + opt = self.options[i] + bw = opt.font:get_width(opt.text) + 2 * BORDER_WIDTH + style.padding.x - for i, opt in self:each_option() do - local lw, lh = opt.font:get_width(opt.text), opt.font:get_height(opt.text) - local bw, bh = (lw + 2 * BORDER_WIDTH + 2 * BORDER_PADDING), (lh + 2 * BORDER_WIDTH + 2 * BORDER_PADDING) - local halfbh = math.floor(bh / 2) - local bx, by = math.max(0, ox - bw), math.max(0, oy + halfh - halfbh) - local fw, fh = bw - 2 * BORDER_WIDTH, bh - 2 * BORDER_WIDTH - local fx, fy = bx + BORDER_WIDTH, by + BORDER_WIDTH - coroutine.yield(i, opt, bx,by,bw,bh, fx,fy,fw,fh) ox = ox - bw - style.padding.x + coroutine.yield(i, opt, ox,oy,bw,bh) end end) end function NagView:on_mouse_moved(mx, my, ...) NagView.super.on_mouse_moved(self, mx, my, ...) - local selected = false - for i, _, x,y,w,h in self:each_visible_option() do + for i, _, x,y,w,h in self:each_option() do if mx >= x and my >= y and mx < x + w and my < y + h then - self.selected = i - selected = true + self:change_hovered(i) break end end - - if not selected then self.selected = nil end end -function NagView:on_mouse_pressed(...) - if not NagView.super.on_mouse_pressed(self, ...) and self.selected then - self.force_focus = false - core.set_active_view(core.last_active_view) - self.submit(self.options[self.selected]) +function NagView:on_mouse_pressed(button, mx, my, clicks) + if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end + for i, _, x,y,w,h in self:each_option() do + if mx >= x and my >= y and mx < x + w and my < y + h then + self:change_hovered(i) + command.perform "dialog:select" + break + end + end +end + +function NagView:on_text_input(text) + if text:lower() == "y" then + command.perform "dialog:select-yes" + elseif text:lower() == "n" then + command.perform "dialog:select-no" end end function NagView:draw() - if self.size.y <= 0 then return end + if self.size.y <= 0 or not self.title then return end self:draw_overlay() self:draw_background(style.nagbar) - -- draw message local ox, oy = self:get_content_offset() - common.draw_text(style.font, style.nagbar_text, self.message, "left", ox + style.padding.x, oy, self.size.x, self.size.y) + ox = ox + style.padding.x + + -- if there are other items, show it + if #self.queue > 0 then + local str = string.format("[%d]", #self.queue) + ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y) + ox = ox + style.padding.x + end + + -- draw message + common.draw_text(style.font, style.nagbar_text, self.message, "left", ox, oy, self.size.x, self.size.y) -- draw buttons - for i, opt, bx,by,bw,bh, fx,fy,fw,fh in self:each_visible_option() do - local fill = i == self.selected and style.nagbar_text or style.nagbar - local text_color = i == self.selected and style.nagbar or style.nagbar_text + for i, opt, bx,by,bw,bh in self:each_option() do + local fw,fh = bw - 2 * BORDER_WIDTH, bh - 2 * BORDER_WIDTH + local fx,fy = bx + BORDER_WIDTH, by + BORDER_WIDTH + -- draw the button renderer.draw_rect(bx,by,bw,bh, style.nagbar_text) + renderer.draw_rect(fx,fy,fw,fh, style.nagbar) - if i ~= self.selected then - renderer.draw_rect(fx,fy,fw,fh, fill) + if i == self.hovered_item then -- draw underline + local uw = fw - 2 * UNDERLINE_MARGIN + local halfuw = uw / 2 + local lx = fx + UNDERLINE_MARGIN + halfuw - (halfuw * self.underline_progress) + local ly = fy + fh - UNDERLINE_MARGIN - UNDERLINE_WIDTH + uw = uw * self.underline_progress + renderer.draw_rect(lx,ly,uw,UNDERLINE_WIDTH, style.nagbar_text) end - common.draw_text(opt.font, text_color, opt.text, "center", fx,fy,fw,fh) + common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh) end end -function NagView:show(title, message, options, on_select, on_cancel) - self.title = title or "Warning" - self.message = message or "Empty?" - self.options = options or {} - if on_cancel then table.insert(options, { key = "cancel", font = style.icon_font, text = "C" }) end - self.force_focus = true - self.submit = function(item) - self.submit = noop -- reset the submit function - if item.key == "cancel" and on_cancel then - on_cancel() - elseif on_select then - on_select(item) - end +local function findindex(tbl, prop) + for i, o in ipairs(tbl) do + if o[prop] then return i end end - core.set_active_view(self) end +function NagView:next() + local opts = table.remove(self.queue, 1) or {} + self.title = opts.title + self.message = opts.message + self.options = opts.options + self.on_selected = opts.on_selected + if self.message and self.options then + self.max_lh = math.max(style.font:get_height(self.message), self:get_options_line_height()) + self:change_hovered(findindex(self.options, "default_yes")) + end + self.force_focus = self.message ~= nil + core.set_active_view(self.message ~= nil and self or core.last_active_view) +end + +function NagView:show(title, message, options, on_select) + local opts = {} + opts.title = assert(title, "No title") + opts.message = assert(message, "No message") + opts.options = assert(options, "No options") + opts.on_selected = on_select or noop + table.insert(self.queue, opts) + if #self.queue > 0 and not self.title then self:next() end +end + +command.add(NagView, { + ["dialog:previous-entry"] = function() + local v = core.active_view + local hover = v.hovered_item or 1 + v:change_hovered(hover == 1 and #v.options or hover - 1) + end, + ["dialog:next-entry"] = function() + local v = core.active_view + local hover = v.hovered_item or 1 + v:change_hovered(hover == #v.options and 1 or hover + 1) + end, + ["dialog:select-yes"] = function() + local v = core.active_view + if v ~= core.nag_view then return end + v:change_hovered(findindex(v.options, "default_yes")) + command.perform "dialog:select" + end, + ["dialog:select-no"] = function() + local v = core.active_view + if v ~= core.nag_view then return end + v:change_hovered(findindex(v.options, "default_no")) + command.perform "dialog:select" + end, + ["dialog:select"] = function() + local v = core.active_view + if v.hovered_item then + v.on_selected(v.options[v.hovered_item]) + v:next() + end + end, +}) + return NagView \ No newline at end of file