diff --git a/data/core/init.lua b/data/core/init.lua index a3e6eba6..c16307c8 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1073,6 +1073,11 @@ function core.blink_reset() end +function core.request_cursor(value) + core.cursor_change_req = value +end + + function core.on_error(err) -- write error to file local fp = io.open(USERDIR .. "/error.txt", "wb") diff --git a/data/core/rootview.lua b/data/core/rootview.lua index 8b3a91ae..b01303c5 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -783,7 +783,7 @@ end function RootView:on_mouse_moved(x, y, dx, dy) if core.active_view == core.nag_view then - system.set_cursor("arrow") + core.request_cursor("arrow") core.active_view:on_mouse_moved(x, y, dx, dy) return end @@ -808,14 +808,14 @@ function RootView:on_mouse_moved(x, y, dx, dy) local div = self.root_node:get_divider_overlapping_point(x, y) local tab_index = node and node:get_tab_overlapping_point(x, y) if node and node:get_scroll_button_index(x, y) then - system.set_cursor("arrow") + core.request_cursor("arrow") elseif div then local axis = (div.type == "hsplit" and "x" or "y") if div.a:is_resizable(axis) and div.b:is_resizable(axis) then - system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev") + core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev") end elseif tab_index then - system.set_cursor("arrow") + core.request_cursor("arrow") if self.dragged_node and self.dragged_node ~= tab_index then local tab = node.views[self.dragged_node] table.remove(node.views, self.dragged_node) @@ -823,7 +823,7 @@ function RootView:on_mouse_moved(x, y, dx, dy) self.dragged_node = tab_index end else - system.set_cursor(node.active_view.cursor) + core.request_cursor(node.active_view.cursor) end end @@ -858,6 +858,10 @@ function RootView:draw() local t = table.remove(self.deferred_draws) t.fn(table.unpack(t)) end + if core.cursor_change_req then + system.set_cursor(core.cursor_change_req) + core.cursor_change_req = nil + end end diff --git a/data/plugins/contextmenu.lua b/data/plugins/contextmenu.lua new file mode 100644 index 00000000..e7a27e7d --- /dev/null +++ b/data/plugins/contextmenu.lua @@ -0,0 +1,285 @@ +-- mod-version:1 -- lite-xl 1.16 +local core = require "core" +local common = require "core.common" +local config = require "core.config" +local command = require "core.command" +local keymap = require "core.keymap" +local style = require "core.style" +local Object = require "core.object" +local RootView = require "core.rootview" + +local border_width = 1 +local divider_width = 1 +local DIVIDER = {} + +local ContextMenu = Object:extend() + +function ContextMenu:new() + self.itemset = {} + self.show_context_menu = false + self.selected = -1 + self.height = 0 + self.position = { x = 0, y = 0 } +end + +local function get_item_size(item) + local lw, lh + if item == DIVIDER then + lw = 0 + lh = divider_width + else + lw = style.font:get_width(item.text) + if item.info then + lw = lw + style.padding.x + style.font:get_width(item.info) + end + lh = style.font:get_height() + style.padding.y + end + return lw, lh +end + +function ContextMenu:register(predicate, items) + 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 + + local width, height = 0, 0 --precalculate the size of context menu + for i, item in ipairs(items) do + if item ~= DIVIDER then + item.info = keymap.reverse_map[item.command] + end + local lw, lh = get_item_size(item) + width = math.max(width, lw) + height = height + lh + end + width = width + style.padding.x * 2 + items.width, items.height = width, height + table.insert(self.itemset, { predicate = predicate, items = items }) +end + +function ContextMenu:show(x, y) + self.items = nil + for _, items in ipairs(self.itemset) do + if items.predicate(x, y) then + self.items = items.items + break + end + end + + if self.items then + local w, h = self.items.width, self.items.height + + -- by default the box is opened on the right and below + if x + w >= core.root_view.size.x then + x = x - w + end + if y + h >= core.root_view.size.y then + y = y - h + end + + self.position.x, self.position.y = x, y + self.show_context_menu = true + return true + end + return false +end + +function ContextMenu:hide() + self.show_context_menu = false + self.items = nil + self.selected = -1 + self.height = 0 +end + +function ContextMenu:each_item() + local x, y, w = self.position.x, self.position.y, self.items.width + local oy = y + return coroutine.wrap(function() + for i, item in ipairs(self.items) do + local _, lh = get_item_size(item) + if y - oy > self.height then break end + coroutine.yield(i, item, x, y, w, lh) + y = y + lh + end + end) +end + +function ContextMenu:on_mouse_moved(px, py) + if not self.show_context_menu then return end + + self.selected = -1 + for i, item, x, y, w, h in self:each_item() do + if px > x and px <= x + w and py > y and py <= y + h then + self.selected = i + break + end + end + if self.selected >= 0 then + core.request_cursor("arrow") + end +end + +function ContextMenu:on_selected(item) + if type(item.command) == "string" then + command.perform(item.command) + else + item.command() + end +end + +function ContextMenu:on_mouse_pressed(button, x, y, clicks) + local selected = (self.items or {})[self.selected] + local caught = false + + self:hide() + if button == "left" then + if selected then + self:on_selected(selected) + caught = true + end + end + + if button == "right" then + caught = self:show(x, y) + end + return caught +end + +-- copied from core.docview +function ContextMenu:move_towards(t, k, dest, rate) + if type(t) ~= "table" then + return self:move_towards(self, t, k, dest, rate) + end + local val = t[k] + if not config.transitions or math.abs(val - dest) < 0.5 then + t[k] = dest + else + rate = rate or 0.5 + if config.fps ~= 60 or config.animation_rate ~= 1 then + local dt = 60 / config.fps + rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt) + end + t[k] = common.lerp(val, dest, rate) + end + if val ~= dest then + core.redraw = true + end +end + +function ContextMenu:update() + if self.show_context_menu then + self:move_towards("height", self.items.height) + end +end + +function ContextMenu:draw() + if not self.show_context_menu then return end + core.root_view:defer_draw(self.draw_context_menu, self) +end + +function ContextMenu:draw_context_menu() + if not self.items then return end + local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height + + renderer.draw_rect( + bx - border_width, + by - border_width, + bw + (border_width * 2), + bh + (border_width * 2), + style.divider + ) + renderer.draw_rect(bx, by, bw, bh, style.background3) + + for i, item, x, y, w, h in self:each_item() do + if item == DIVIDER then + renderer.draw_rect(x, y, w, h, style.caret) + else + if i == self.selected then + renderer.draw_rect(x, y, w, h, style.selection) + end + + common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h) + if item.info then + common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h) + end + end + end +end + + +local menu = ContextMenu() +local root_view_on_mouse_pressed = RootView.on_mouse_pressed +local root_view_on_mouse_moved = RootView.on_mouse_moved +local root_view_update = RootView.update +local root_view_draw = RootView.draw + +function RootView:on_mouse_moved(...) + root_view_on_mouse_moved(self, ...) + menu:on_mouse_moved(...) +end + +-- this function is mostly copied from lite-xl's source +function RootView:on_mouse_pressed(button, x,y, clicks) + local div = self.root_node:get_divider_overlapping_point(x, y) + if div then + self.dragged_divider = div + return + end + local node = self.root_node:get_child_overlapping_point(x, y) + if node.hovered_scroll_button > 0 then + node:scroll_tabs(node.hovered_scroll_button) + return + end + local idx = node:get_tab_overlapping_point(x, y) + if idx then + if button == "middle" or node.hovered_close == idx then + node:close_view(self.root_node, node.views[idx]) + else + self.dragged_node = idx + node:set_active_view(node.views[idx]) + end + else + core.set_active_view(node.active_view) + -- send to context menu first + if not menu:on_mouse_pressed(button, x, y, clicks) then + node.active_view:on_mouse_pressed(button, x, y, clicks) + end + end +end + +function RootView:update(...) + root_view_update(self, ...) + menu:update() +end + +function RootView:draw(...) + root_view_draw(self, ...) + menu:draw() +end + +command.add(nil, { + ["context:show"] = function() + menu:show(core.active_view.position.x, core.active_view.position.y) + end +}) + +keymap.add { + ["menu"] = "context:show" +} + +if require("plugins.scale") then + menu:register("core.docview", { + { text = "Font +", command = "scale:increase" }, + { text = "Font -", command = "scale:decrease" }, + { text = "Font Reset", command = "scale:reset" }, + DIVIDER, + { text = "Find", command = "find-replace:find" }, + { text = "Replace", command = "find-replace:replace" }, + DIVIDER, + { text = "Find Pattern", command = "find-replace:find-pattern" }, + { text = "Replace Pattern", command = "find-replace:replace-pattern" }, + }) +end diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua new file mode 100644 index 00000000..11064235 --- /dev/null +++ b/data/plugins/scale.lua @@ -0,0 +1,100 @@ +-- mod-version:1 -- lite-xl 1.16 +local core = require "core" +local common = require "core.common" +local command = require "core.command" +local config = require "core.config" +local keymap = require "core.keymap" +local style = require "core.style" +local RootView = require "core.rootview" +local CommandView = require "core.commandview" + +config.scale_mode = "code" +config.scale_use_mousewheel = true + +local scale_level = 0 +local scale_steps = 0.05 + +local current_scale = SCALE +local default_scale = SCALE + +local function set_scale(scale) + scale = common.clamp(scale, 0.2, 6) + + -- save scroll positions + local scrolls = {} + for _, view in ipairs(core.root_view.root_node:get_children()) do + local n = view:get_scrollable_size() + if n ~= math.huge and not view:is(CommandView) and n > view.size.y then + scrolls[view] = view.scroll.y / (n - view.size.y) + end + end + + local s = scale / current_scale + current_scale = scale + + if config.scale_mode == "ui" then + SCALE = scale + + style.padding.x = style.padding.x * s + style.padding.y = style.padding.y * s + style.divider_size = style.divider_size * s + style.scrollbar_size = style.scrollbar_size * s + style.caret_width = style.caret_width * s + style.tab_width = style.tab_width * s + + for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do + renderer.font.set_size(style[name], s * style[name]:get_size()) + end + else + renderer.font.set_size(style.code_font, s * style.code_font:get_size()) + end + + -- restore scroll positions + for view, n in pairs(scrolls) do + view.scroll.y = n * (view:get_scrollable_size() - view.size.y) + view.scroll.to.y = view.scroll.y + end + + core.redraw = true +end + + +local on_mouse_wheel = RootView.on_mouse_wheel + +function RootView:on_mouse_wheel(d, ...) + if keymap.modkeys["ctrl"] and config.scale_use_mousewheel then + if d < 0 then command.perform "scale:decrease" end + if d > 0 then command.perform "scale:increase" end + else + return on_mouse_wheel(self, d, ...) + end +end + +local function res_scale() + scale_level = 0 + set_scale(default_scale) +end + +local function inc_scale() + scale_level = scale_level + 1 + set_scale(default_scale + scale_level * scale_steps) +end + +local function dec_scale() + scale_level = scale_level - 1 + set_scale(default_scale + scale_level * scale_steps) +end + + +command.add(nil, { + ["scale:reset" ] = function() res_scale() end, + ["scale:decrease"] = function() dec_scale() end, + ["scale:increase"] = function() inc_scale() end, +}) + +keymap.add { + ["ctrl+0"] = "scale:reset", + ["ctrl+-"] = "scale:decrease", + ["ctrl+="] = "scale:increase", +} + diff --git a/src/api/renderer_font.c b/src/api/renderer_font.c index 47ea97a3..f510da70 100644 --- a/src/api/renderer_font.c +++ b/src/api/renderer_font.c @@ -65,7 +65,7 @@ static int f_set_tab_size(lua_State *L) { static int f_gc(lua_State *L) { FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); - font_desc_free(self); + font_desc_clear(self); return 0; } @@ -104,6 +104,22 @@ static int f_get_height(lua_State *L) { } +static int f_get_size(lua_State *L) { + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); + lua_pushnumber(L, self->size); + return 1; +} + + +static int f_set_size(lua_State *L) { + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); + float new_size = luaL_checknumber(L, 2); + font_desc_clear(self); + self->size = new_size; + return 0; +} + + static const luaL_Reg lib[] = { { "__gc", f_gc }, { "load", f_load }, @@ -112,6 +128,8 @@ static const luaL_Reg lib[] = { { "get_width_subpixel", f_get_width_subpixel }, { "get_height", f_get_height }, { "subpixel_scale", f_subpixel_scale }, + { "get_size", f_get_size }, + { "set_size", f_set_size }, { NULL, NULL } }; diff --git a/src/fontdesc.c b/src/fontdesc.c index d1d0825f..44460a6d 100644 --- a/src/fontdesc.c +++ b/src/fontdesc.c @@ -18,11 +18,12 @@ void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsig font_desc->cache_last_index = 0; /* Normally no need to initialize. */ } -void font_desc_free(FontDesc *font_desc) { +void font_desc_clear(FontDesc *font_desc) { for (int i = 0; i < font_desc->cache_length; i++) { ren_free_font(font_desc->cache[i].font); } font_desc->cache_length = 0; + font_desc->cache_last_index = 0; } void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) { diff --git a/src/fontdesc.h b/src/fontdesc.h index 2f4702ab..bf591801 100644 --- a/src/fontdesc.h +++ b/src/fontdesc.h @@ -26,7 +26,7 @@ void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsig int font_desc_alloc_size(const char *filename); int font_desc_get_tab_size(FontDesc *font_desc); void font_desc_set_tab_size(FontDesc *font_desc, int tab_size); -void font_desc_free(FontDesc *font_desc); +void font_desc_clear(FontDesc *font_desc); RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale); #endif