Scale fonts context menu (#246)
* Retrieve scale plugin from lite-plugins * New implementation of scale plugin and font C API Introduce two new C API functions, renderer.font.get_size and set_size respectively to get the font size and to set the size to a new value. Using these functions we don't need to know the name of the font but we can just change their size. Adapt the scale plugin to use the new C API function with minor adaptations in the logic. Use smaller step to scale fonts. Rename font_desc_free function, previous name was misleading as only the cached resources are freed. * Add contextmenu plugin from takase From https://github.com/takase1121/lite-contextmenu Adapted to show font scaling commands and find/replace commands. i# testing.lua * Fix the cursor flickering with contextmenu To avoid flickering of the cursor when using the context menu we add a new function `core.request_cursor` that just take note of the cursor requested. The cursor will be actually changed only in root_view:draw() method only when all the drawing operations are done. This means the cursor will be changed only once per frame and only the most recent cursor change request will take effect. * Remove unneeded scale plugin return functions
This commit is contained in:
parent
d0adb748a6
commit
b046afccf9
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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",
|
||||
}
|
||||
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue