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:
Francesco 2021-06-03 22:49:37 +02:00 committed by GitHub
parent d0adb748a6
commit b046afccf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 421 additions and 8 deletions

View File

@ -1073,6 +1073,11 @@ function core.blink_reset()
end end
function core.request_cursor(value)
core.cursor_change_req = value
end
function core.on_error(err) function core.on_error(err)
-- write error to file -- write error to file
local fp = io.open(USERDIR .. "/error.txt", "wb") local fp = io.open(USERDIR .. "/error.txt", "wb")

View File

@ -783,7 +783,7 @@ end
function RootView:on_mouse_moved(x, y, dx, dy) function RootView:on_mouse_moved(x, y, dx, dy)
if core.active_view == core.nag_view then 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) core.active_view:on_mouse_moved(x, y, dx, dy)
return return
end 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 div = self.root_node:get_divider_overlapping_point(x, y)
local tab_index = node and node:get_tab_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 if node and node:get_scroll_button_index(x, y) then
system.set_cursor("arrow") core.request_cursor("arrow")
elseif div then elseif div then
local axis = (div.type == "hsplit" and "x" or "y") local axis = (div.type == "hsplit" and "x" or "y")
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then 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 end
elseif tab_index then elseif tab_index then
system.set_cursor("arrow") core.request_cursor("arrow")
if self.dragged_node and self.dragged_node ~= tab_index then if self.dragged_node and self.dragged_node ~= tab_index then
local tab = node.views[self.dragged_node] local tab = node.views[self.dragged_node]
table.remove(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 self.dragged_node = tab_index
end end
else else
system.set_cursor(node.active_view.cursor) core.request_cursor(node.active_view.cursor)
end end
end end
@ -858,6 +858,10 @@ function RootView:draw()
local t = table.remove(self.deferred_draws) local t = table.remove(self.deferred_draws)
t.fn(table.unpack(t)) t.fn(table.unpack(t))
end end
if core.cursor_change_req then
system.set_cursor(core.cursor_change_req)
core.cursor_change_req = nil
end
end end

View File

@ -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

100
data/plugins/scale.lua Normal file
View File

@ -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",
}

View File

@ -65,7 +65,7 @@ static int f_set_tab_size(lua_State *L) {
static int f_gc(lua_State *L) { static int f_gc(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
font_desc_free(self); font_desc_clear(self);
return 0; 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[] = { static const luaL_Reg lib[] = {
{ "__gc", f_gc }, { "__gc", f_gc },
{ "load", f_load }, { "load", f_load },
@ -112,6 +128,8 @@ static const luaL_Reg lib[] = {
{ "get_width_subpixel", f_get_width_subpixel }, { "get_width_subpixel", f_get_width_subpixel },
{ "get_height", f_get_height }, { "get_height", f_get_height },
{ "subpixel_scale", f_subpixel_scale }, { "subpixel_scale", f_subpixel_scale },
{ "get_size", f_get_size },
{ "set_size", f_set_size },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@ -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. */ 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++) { for (int i = 0; i < font_desc->cache_length; i++) {
ren_free_font(font_desc->cache[i].font); ren_free_font(font_desc->cache[i].font);
} }
font_desc->cache_length = 0; font_desc->cache_length = 0;
font_desc->cache_last_index = 0;
} }
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) { void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {

View File

@ -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_alloc_size(const char *filename);
int font_desc_get_tab_size(FontDesc *font_desc); int font_desc_get_tab_size(FontDesc *font_desc);
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size); 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); RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
#endif #endif