Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 116 additions & 129 deletions library/lua/gui/widgets/edit_field.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,61 @@ local gui = require('gui')
local utils = require('utils')
local Widget = require('gui.widgets.widget')
local HotkeyLabel = require('gui.widgets.labels.hotkey_label')
local TextArea = require('gui.widgets.text_area')
local WrappedText = require('gui.widgets.text_area.wrapped_text')

local getval = utils.getval

OneLineWrappedText = defclass(OneLineWrappedText, WrappedText)

function OneLineWrappedText:update(text)
self.lines = {text}
end

TextFieldArea = defclass(TextFieldArea, TextArea)
TextFieldArea.ATTRS{
on_char = DEFAULT_NIL,
key = DEFAULT_NIL,
on_submit = DEFAULT_NIL,
on_submit2 = DEFAULT_NIL,
modal = false,
}

function TextFieldArea:onInput(keys)
if self.on_char and keys._STRING and keys._STRING ~= 0 then
if not self.on_char(string.char(keys._STRING), self:getText()) then
return self.modal
end
end

if keys.SELECT or keys.SELECT_ALL then
if self.key then
self:setFocus(false)
end

if keys.SELECT_ALL then
if self.on_submit2 then
self.on_submit2(self:getText())
return true
end
else
if self.on_submit then
self.on_submit(self:getText())
return true
end
end

return not not self.key
end

return TextFieldArea.super.onInput(self, keys)
end

function TextFieldArea:getPreferredFocusState()
-- allow EditField to manage focus
return false
end

----------------
-- Edit field --
----------------
Expand Down Expand Up @@ -52,179 +104,114 @@ end

function EditField:init()
local function on_activate()
self.saved_text = self.text
self:setFocus(true)
end

self.start_pos = 1
self.cursor = #self.text + 1
self.ignore_keys = self.ignore_keys or {}

self.label = HotkeyLabel{
frame={t=0,l=0},
key=self.key,
key_sep=self.key_sep,
label=self.label_text,
on_activate=self.key and on_activate or nil
}
self.text_area = TextFieldArea{
one_line_mode=true,
frame={t=0,r=0},
init_text=self.text or '',
text_pen=self.text_pen or COLOR_LIGHTCYAN,
modal=self.modal,
on_char=self.on_char,
key = self.key,
on_submit = self.on_submit,
on_submit2 = self.on_submit2,
on_focus = self:callback('onFocus'),
on_unfocus = self.on_unfocus,
ignore_keys={
table.unpack(self.ignore_keys)
},
on_text_change=self:callback('onTextAreaTextChange'),
on_cursor_change=function(cursor) self.cursor = cursor end
}

self:addviews{self.label, self.text_area}

self.text_area.frame.l = self.label:getTextWidth()
end

self:addviews{HotkeyLabel{frame={t=0,l=0},
key=self.key,
key_sep=self.key_sep,
label=self.label_text,
on_activate=self.key and on_activate or nil}}
function EditField:onFocus()
self.saved_text = self.text

if self.on_focus then
self:on_focus()
end
end

function EditField:getPreferredFocusState()
return not self.key
end

function EditField:setCursor(cursor)
if not cursor or cursor > #self.text then
self.cursor = #self.text + 1
return
if not cursor then
cursor = #self.text + 1
end
self.cursor = math.max(1, cursor)
self.cursor = cursor

self.text_area:setCursor(cursor)
self.cursor = self.text_area:getCursor()
end

function EditField:setText(text, cursor)
text = text or ''

local old = self.text
self.text = text
self.text_area:setText(text)

self:setCursor(cursor)
if self.on_change and text ~= old then
self.on_change(self.text, old)
end
end

function EditField:postUpdateLayout()
self.text_offset = self.subviews[1]:getTextWidth()
end

---@param dc gui.Painter
function EditField:onRenderBody(dc)
dc:pen(self.text_pen or COLOR_LIGHTCYAN)

local cursor_char = '_'
if not getval(self.active) or not self.focus or gui.blink_visible(300) then
cursor_char = (self.cursor > #self.text) and ' ' or
self.text:sub(self.cursor, self.cursor)
end
local txt = self.text:sub(1, self.cursor - 1) .. cursor_char ..
self.text:sub(self.cursor + 1)
local max_width = dc.width - self.text_offset
self.start_pos = 1
if #txt > max_width then
-- get the substring in the vicinity of the cursor
max_width = max_width - 2
local half_width = math.floor(max_width/2)
local start_pos = math.max(1, self.cursor-half_width)
local end_pos = math.min(#txt, self.cursor+half_width-1)
if self.cursor + half_width > #txt then
start_pos = #txt - (max_width - 1)
end
if self.cursor - half_width <= 1 then
end_pos = max_width + 1
function EditField:onTextAreaTextChange(text)
if self.text ~= text then
local old = self.text
self.text = text
if self.on_change then
self.on_change(self.text, old)
end
self.start_pos = start_pos > 1 and start_pos - 1 or start_pos
txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27),
txt:sub(start_pos, end_pos),
end_pos == #txt and '' or string.char(26))
end
dc:advance(self.text_offset):string(txt)
end

function EditField:setFocus(focus)
self.text_area:setFocus(focus)
end

function EditField:insert(text)
local old = self.text
self:setText(old:sub(1,self.cursor-1)..text..old:sub(self.cursor),
self.cursor + #text)
self:setText(
old:sub(1,self.cursor-1)..text..old:sub(self.cursor),
self.cursor + #text
)
end

function EditField:onInput(keys)
if not self.focus then
-- only react to our hotkey
if not self.text_area.focus then
return self:inputToSubviews(keys)
end

if self.ignore_keys then
for _,ignore_key in ipairs(self.ignore_keys) do
if keys[ignore_key] then return false end
end
end

if self.key and (keys.LEAVESCREEN or keys._MOUSE_R) then
self:setText(self.saved_text)
self:setFocus(false)
return true
end

if keys.SELECT or keys.SELECT_ALL then
if self.key then
self:setFocus(false)
end
if keys.SELECT_ALL then
if self.on_submit2 then
self.on_submit2(self.text)
return true
end
else
if self.on_submit then
self.on_submit(self.text)
return true
end
end
return not not self.key
elseif keys.CUSTOM_DELETE then
local old = self.text
local del_pos = self.cursor
if del_pos <= #old then
self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1), del_pos)
end
return true
elseif keys._STRING then
local old = self.text
if keys._STRING == 0 then
-- handle backspace
local del_pos = self.cursor - 1
if del_pos > 0 then
self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1), del_pos)
end
else
local cv = string.char(keys._STRING)
if not self.on_char or self.on_char(cv, old) then
self:insert(cv)
elseif self.on_char then
return self.modal
end
end
if self.text_area:onInput(keys) then
return true
elseif keys.KEYBOARD_CURSOR_LEFT then
self:setCursor(self.cursor - 1)
return true
elseif keys.CUSTOM_CTRL_LEFT then -- back one word
local _, prev_word_end = self.text:sub(1, self.cursor-1):
find('.*[%w_%-][^%w_%-]')
self:setCursor(prev_word_end or 1)
return true
elseif keys.CUSTOM_HOME then
self:setCursor(1)
return true
elseif keys.KEYBOARD_CURSOR_RIGHT then
self:setCursor(self.cursor + 1)
return true
elseif keys.CUSTOM_CTRL_RIGHT then -- forward one word
local _,next_word_start = self.text:find('[^%w_%-][%w_%-]', self.cursor)
self:setCursor(next_word_start)
return true
elseif keys.CUSTOM_END then
self:setCursor()
return true
elseif keys.CUSTOM_CTRL_C then
dfhack.internal.setClipboardTextCp437(self.text)
return true
elseif keys.CUSTOM_CTRL_X then
dfhack.internal.setClipboardTextCp437(self.text)
self:setText('')
return true
elseif keys.CUSTOM_CTRL_V then
self:insert(dfhack.internal.getClipboardTextCp437())
return true
elseif keys._MOUSE_L_DOWN then
local mouse_x = self:getMousePos()
if mouse_x then
self:setCursor(self.start_pos + mouse_x - (self.text_offset or 0))
return true
end
end

-- if we're modal, then unconditionally eat all the input
Expand Down
40 changes: 32 additions & 8 deletions library/lua/gui/widgets/text_area.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ TextArea.ATTRS{

function TextArea:init()
self.render_start_line_y = 1
self.render_start_x = 1

self.text_area = TextAreaContent{
frame={l=0,r=3,t=0},
frame={l=0,r=self.one_line_mode and 0 or 3,t=0},
text=self.init_text,

text_pen=self.text_pen,
Expand All @@ -35,7 +36,10 @@ function TextArea:init()
one_line_mode=self.one_line_mode,

on_text_change=function (text, old_text)
self:updateLayout()
if self.frame_body then
self:updateLayout()
end

if self.on_text_change then
self.on_text_change(text, old_text)
end
Expand Down Expand Up @@ -85,12 +89,25 @@ function TextArea:onCursorChange(cursor, old_cursor)
self.text_area.cursor
)

if y >= self.render_start_line_y + self.text_area.frame_body.height then
self:updateScrollbar(
y - self.text_area.frame_body.height + 1
)
elseif (y < self.render_start_line_y) then
self:updateScrollbar(y)
if self.text_area.frame_body then
if y >= self.render_start_line_y + self.text_area.frame_body.height then
self:updateScrollbar(
y - self.text_area.frame_body.height + 1
)
elseif (y < self.render_start_line_y) then
self:updateScrollbar(y)
end

if self.one_line_mode then
local x_screen_offset_right = math.max(0, x - self.render_start_x - self.text_area.frame_body.width + 1)
local x_screen_offset_left = math.max(0, self.render_start_x - x)

if x_screen_offset_right > 0 then
self.render_start_x = self.render_start_x + x_screen_offset_right
elseif x_screen_offset_left > 0 then
self.render_start_x = self.render_start_x - x_screen_offset_left
end
end
end

if self.on_cursor_change then
Expand Down Expand Up @@ -164,6 +181,9 @@ end

function TextArea:renderSubviews(dc)
self.text_area.frame_body.y1 = self.frame_body.y1-(self.render_start_line_y - 1)
if self.one_line_mode then
self.text_area.frame_body.x1 = self.frame_body.x1-(self.render_start_x - 1)
end
-- only visible lines of text_area will be rendered
TextArea.super.renderSubviews(self, dc)
end
Expand All @@ -177,6 +197,10 @@ function TextArea:onInput(keys)
self:setFocus(true)
end

if not self.focus then
return false
end

return TextArea.super.onInput(self, keys)
end

Expand Down
Loading
Loading