From 410288255f6ac1faf94aee271813884aafdefd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Sun, 1 Sep 2024 20:07:26 +0200 Subject: [PATCH 01/15] Add simple notes plugin, with adding notes and render stub --- internal/journal/text_editor.lua | 10 +- notes.lua | 181 +++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 notes.lua diff --git a/internal/journal/text_editor.lua b/internal/journal/text_editor.lua index 8bdd0aeb7..7b5a422c3 100644 --- a/internal/journal/text_editor.lua +++ b/internal/journal/text_editor.lua @@ -169,14 +169,14 @@ function TextEditor:setCursor(cursor_offset) end function TextEditor:getPreferredFocusState() - return true + return self.parent_view.focus end function TextEditor:postUpdateLayout() self:updateScrollbar(self.render_start_line_y) if self.subviews.text_area.cursor == nil then - local cursor = self.init_cursor or #self.text + 1 + local cursor = self.init_cursor or #self.init_text + 1 self.subviews.text_area:setCursor(cursor) self:scrollToCursor(cursor) end @@ -234,6 +234,10 @@ function TextEditor:onInput(keys) return self.subviews.scrollbar:onInput(keys) end + if keys._MOUSE_L then + self:setFocus(true) + end + return TextEditor.super.onInput(self, keys) end @@ -629,6 +633,8 @@ function TextEditorView:onInput(keys) self:paste() self.history:store(HISTORY_ENTRY.OTHER, self.text, self.cursor) return true + else + return TextEditor.super.onInput(self, keys) end end diff --git a/notes.lua b/notes.lua new file mode 100644 index 000000000..fd2532af9 --- /dev/null +++ b/notes.lua @@ -0,0 +1,181 @@ +--@ module = true + +local gui = require('gui') +local widgets = require('gui.widgets') +local textures = require('gui.textures') +local overlay = require('plugins.overlay') +local guidm = require('gui.dwarfmode') +local text_editor = reqscript('internal/journal/text_editor') + +-- local green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12, true), + +-- NotesView = defclass(NotesView, gui.View) +-- NotesView.ATTRS{} + +NotesOverlay = defclass(NotesOverlay, overlay.OverlayWidget) +NotesOverlay.ATTRS{ + desc='Render map notes.', + viewscreens='dwarfmode', + default_enabled=true, + -- TODO increase to 30 seconds + overlay_onupdate_max_freq_seconds=1, + hotspot=true, + fullscreen=true, +} + +function NotesOverlay:init() +end + +function NotesOverlay:onRenderFrame(dc) + if not df.global.pause_state and not dfhack.screen.inGraphicsMode() then + return + end + + dc:map(true) + -- local texpos = dfhack.textures.getTexposByHandle(green_pin[1]) + local texpos = textures.tp_green_pin(1) + local color, ch = COLOR_RED, 'X' + dc:pen({ch='X', fg=COLOR_GREEN, bg=COLOR_BLACK, tile=texpos}) + + local viewport = guidm.Viewport.get() + + for _, point in ipairs(df.global.plotinfo.waypoints.points) do + if viewport:isVisible(point.pos) then + local pos = viewport:tileToScreen(point.pos) + dc + :seek(pos.x, pos.y) + :tile() + end + end + + dc:map(false) + +end + +NoteManager = defclass(NoteManager, gui.ZScreen) +NoteManager.ATTRS{ + focus_path='hotspot/menu', + hotspot_frame=DEFAULT_NIL, +} + +function NoteManager:init() + self:addviews{ + widgets.Window{ + frame={w=35,h=20}, + frame_inset={t=1}, + autoarrange_subviews=false, + subviews={ + widgets.HotkeyLabel { + key='CUSTOM_ALT_N', + label='Name', + frame={t=0}, + on_activate=function() self.subviews.name:setFocus(true) end, + }, + text_editor.TextEditor{ + view_id='name', + focus_path='notes/name', + frame={t=1,h=3}, + frame_style=gui.FRAME_INTERIOR, + frame_style_b=nil,NoteManager + }, + widgets.HotkeyLabel { + key='CUSTOM_ALT_C', + label='Comment', + frame={t=4}, + on_activate=function() self.subviews.comment:setFocus(true) end, + }, + text_editor.TextEditor{ + view_id='comment', + frame={t=5,b=3}, + focus_path='notes/comment', + frame_style=gui.FRAME_INTERIOR, + }, + widgets.Panel{ + view_id='buttons', + frame={b=0,h=3}, + autoarrange_subviews=true, + subviews={ + widgets.TextButton{ + view_id='Create', + frame={h=1}, + label='Create', + key='CUSTOM_ALT_S', + on_activate=function() self:createNote() end, + enabled=function() return #self.subviews.name:getText() > 0 end, + }, + widgets.TextButton{ + view_id='cancel', + frame={h=1}, + label='Cancel', + key='LEAVESCREEN' + }, + widgets.TextButton{ + view_id='delete', + frame={h=1}, + label='Delete', + key='CUSTOM_ALT_D', + }, + } + } + }, + }, + } +end + +function NoteManager:createNote() + local name = self.subviews.name:getText() + local comment = self.subviews.comment:getText() + + if #name == 0 then + print('Note need at least a name') + return + end + + local waypoints = df.global.plotinfo.waypoints + local notes = df.global.plotinfo.waypoints.points + + local x, y, z = pos2xyz(df.global.cursor) + if x == nil then + print('Enable keyboard cursor to add a note.') + return + end + + notes:insert("#", { + new=true, + + id = waypoints.next_point_id, + tile=88, + fg_color=7, + bg_color=0, + name=name, + comment=comment, + pos=xyz2pos(x, y, z) + }) + waypoints.next_point_id = waypoints.next_point_id + 1 + self:dismiss() +end + +-- register widgets +OVERLAY_WIDGETS = { + map_notes=NotesOverlay +} + +local function main(args) + if #args == 0 then + return + end + + if args[1] == 'add' then + local x = pos2xyz(df.global.cursor) + if x == nil then + print('Enable keyboard cursor to add a note.') + return + end + + return NoteManager{}:show() + end +end + +if not dfhack_flags.module then + main({...}) +end From 4503fc4628473bbfc83910e0fce5e48c6fba0cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Sun, 1 Sep 2024 20:35:30 +0200 Subject: [PATCH 02/15] Alow `notes` plugin to edit existing notes --- internal/journal/table_of_contents.lua | 4 +- notes.lua | 66 ++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/internal/journal/table_of_contents.lua b/internal/journal/table_of_contents.lua index 67e12209a..c1e2096df 100644 --- a/internal/journal/table_of_contents.lua +++ b/internal/journal/table_of_contents.lua @@ -37,12 +37,12 @@ function TableOfContents:init() local function can_prev() local toc = self.subviews.table_of_contents - return #toc:getChoices() > 0 and toc:getSelected() > 1 + return #toc:getChoices() > 0 end local function can_next() local toc = self.subviews.table_of_contents local num_choices = #toc:getChoices() - return num_choices > 0 and toc:getSelected() < num_choices + return num_choices > 0 end self:addviews{ diff --git a/notes.lua b/notes.lua index fd2532af9..cf8752570 100644 --- a/notes.lua +++ b/notes.lua @@ -26,6 +26,20 @@ NotesOverlay.ATTRS{ function NotesOverlay:init() end +function NotesOverlay:onInput(keys) + if keys._MOUSE_L then + local pos = dfhack.gui.getMousePos() + + local notes = df.global.plotinfo.waypoints.points + for _, note in pairs(notes) do + if same_xyz(note.pos, pos) then + return NoteManager{note=note}:show() + end + end + + end +end + function NotesOverlay:onRenderFrame(dc) if not df.global.pause_state and not dfhack.screen.inGraphicsMode() then return @@ -56,9 +70,12 @@ NoteManager = defclass(NoteManager, gui.ZScreen) NoteManager.ATTRS{ focus_path='hotspot/menu', hotspot_frame=DEFAULT_NIL, + note=DEFAULT_NIL, } function NoteManager:init() + local edit_mode = self.note ~= nil + self:addviews{ widgets.Window{ frame={w=35,h=20}, @@ -76,7 +93,8 @@ function NoteManager:init() focus_path='notes/name', frame={t=1,h=3}, frame_style=gui.FRAME_INTERIOR, - frame_style_b=nil,NoteManager + frame_style_b=nil, + init_text=self.note and self.note.name or '' }, widgets.HotkeyLabel { key='CUSTOM_ALT_C', @@ -89,13 +107,21 @@ function NoteManager:init() frame={t=5,b=3}, focus_path='notes/comment', frame_style=gui.FRAME_INTERIOR, + init_text=self.note and self.note.comment or '' }, widgets.Panel{ view_id='buttons', frame={b=0,h=3}, autoarrange_subviews=true, subviews={ - widgets.TextButton{ + edit_mode and widgets.TextButton{ + view_id='Save', + frame={h=1}, + label='Save', + key='CUSTOM_ALT_S', + on_activate=function() self:saveNote() end, + enabled=function() return #self.subviews.name:getText() > 0 end, + } or widgets.TextButton{ view_id='Create', frame={h=1}, label='Create', @@ -109,12 +135,12 @@ function NoteManager:init() label='Cancel', key='LEAVESCREEN' }, - widgets.TextButton{ + edit_mode and widgets.TextButton{ view_id='delete', frame={h=1}, label='Delete', key='CUSTOM_ALT_D', - }, + } or nil, } } }, @@ -123,6 +149,12 @@ function NoteManager:init() end function NoteManager:createNote() + local x, y, z = pos2xyz(df.global.cursor) + if x == nil then + print('Enable keyboard cursor to add a note.') + return + end + local name = self.subviews.name:getText() local comment = self.subviews.comment:getText() @@ -134,12 +166,6 @@ function NoteManager:createNote() local waypoints = df.global.plotinfo.waypoints local notes = df.global.plotinfo.waypoints.points - local x, y, z = pos2xyz(df.global.cursor) - if x == nil then - print('Enable keyboard cursor to add a note.') - return - end - notes:insert("#", { new=true, @@ -155,6 +181,26 @@ function NoteManager:createNote() self:dismiss() end +function NoteManager:saveNote() + if self.note == nil then + return + end + + local name = self.subviews.name:getText() + local comment = self.subviews.comment:getText() + + if #name == 0 then + print('Note need at least a name') + return + end + + local notes = df.global.plotinfo.waypoints.points + self.note.name = name + self.note.comment = comment + + self:dismiss() +end + -- register widgets OVERLAY_WIDGETS = { map_notes=NotesOverlay From ea3b50eca634f9048e704592f012bac7abf89e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Tue, 3 Sep 2024 08:03:50 +0200 Subject: [PATCH 03/15] Improve notes overlay performance --- notes.lua | 108 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/notes.lua b/notes.lua index cf8752570..9b6589669 100644 --- a/notes.lua +++ b/notes.lua @@ -7,25 +7,32 @@ local overlay = require('plugins.overlay') local guidm = require('gui.dwarfmode') local text_editor = reqscript('internal/journal/text_editor') --- local green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12, true), - --- NotesView = defclass(NotesView, gui.View) --- NotesView.ATTRS{} +local green_pin = dfhack.textures.loadTileset('hack/data/art/note-green-pin.png', 32, 32, true) NotesOverlay = defclass(NotesOverlay, overlay.OverlayWidget) NotesOverlay.ATTRS{ desc='Render map notes.', viewscreens='dwarfmode', default_enabled=true, - -- TODO increase to 30 seconds - overlay_onupdate_max_freq_seconds=1, - hotspot=true, + overlay_onupdate_max_freq_seconds=30, fullscreen=true, } function NotesOverlay:init() + self.notes = {} + self:reloadVisibleNotes() +end + +function NotesOverlay:overlay_onupdate() + self:reloadVisibleNotes() +end + +function NotesOverlay:overlay_trigger() + print('called') + -- self:reloadVisibleNotes() end + function NotesOverlay:onInput(keys) if keys._MOUSE_L then local pos = dfhack.gui.getMousePos() @@ -33,37 +40,66 @@ function NotesOverlay:onInput(keys) local notes = df.global.plotinfo.waypoints.points for _, note in pairs(notes) do if same_xyz(note.pos, pos) then - return NoteManager{note=note}:show() + NoteManager{ + note=note, + on_update=function() self:reloadVisibleNotes() end + }:show() end end end end +function NotesOverlay:viewportChanged() + return self.viewport_pos.x ~= df.global.window_x or + self.viewport_pos.y ~= df.global.window_y or + self.viewport_pos.z ~= df.global.window_z +end + function NotesOverlay:onRenderFrame(dc) if not df.global.pause_state and not dfhack.screen.inGraphicsMode() then return end + if self:viewportChanged() then + self:reloadVisibleNotes() + end + dc:map(true) - -- local texpos = dfhack.textures.getTexposByHandle(green_pin[1]) - local texpos = textures.tp_green_pin(1) - local color, ch = COLOR_RED, 'X' - dc:pen({ch='X', fg=COLOR_GREEN, bg=COLOR_BLACK, tile=texpos}) + + local texpos = dfhack.textures.getTexposByHandle(green_pin[1]) + dc:pen({fg=COLOR_BLACK, bg=COLOR_LIGHTCYAN, tile=texpos}) + + for _, point in pairs(self.notes) do + dc + :seek(point.pos.x, point.pos.y) + :char('N') + end + + dc:map(false) +end + +function NotesOverlay:reloadVisibleNotes() + print('reloading notes') + self.notes = {} local viewport = guidm.Viewport.get() + self.viewport_pos = { + x=df.global.window_x, + y=df.global.window_y, + z=df.global.window_z + } for _, point in ipairs(df.global.plotinfo.waypoints.points) do if viewport:isVisible(point.pos) then local pos = viewport:tileToScreen(point.pos) - dc - :seek(pos.x, pos.y) - :tile() + table.insert(self.notes, { + name=point.name, + comment=point.comment, + pos=pos + }) end end - - dc:map(false) - end NoteManager = defclass(NoteManager, gui.ZScreen) @@ -71,6 +107,7 @@ NoteManager.ATTRS{ focus_path='hotspot/menu', hotspot_frame=DEFAULT_NIL, note=DEFAULT_NIL, + on_update=DEFAULT_NIL, } function NoteManager:init() @@ -129,17 +166,12 @@ function NoteManager:init() on_activate=function() self:createNote() end, enabled=function() return #self.subviews.name:getText() > 0 end, }, - widgets.TextButton{ - view_id='cancel', - frame={h=1}, - label='Cancel', - key='LEAVESCREEN' - }, edit_mode and widgets.TextButton{ view_id='delete', frame={h=1}, label='Delete', key='CUSTOM_ALT_D', + on_activate=function() self:deleteNote() end, } or nil, } } @@ -178,6 +210,11 @@ function NoteManager:createNote() pos=xyz2pos(x, y, z) }) waypoints.next_point_id = waypoints.next_point_id + 1 + + if self.on_update then + self.on_update() + end + self:dismiss() end @@ -198,6 +235,29 @@ function NoteManager:saveNote() self.note.name = name self.note.comment = comment + if self.on_update then + self.on_update() + end + + self:dismiss() +end + +function NoteManager:deleteNote() + if self.note == nil then + return + end + + for ind, note in pairs(df.global.plotinfo.waypoints.points) do + if note == self.note then + df.global.plotinfo.waypoints.points:erase(ind) + break + end + end + + if self.on_update then + self.on_update() + end + self:dismiss() end From 50b53bc3ee140e0301ac42f9de223137648b7a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Tue, 3 Sep 2024 09:03:03 +0200 Subject: [PATCH 04/15] Allow to iterate map notes on same tile by click --- notes.lua | 71 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/notes.lua b/notes.lua index 9b6589669..025cc2c81 100644 --- a/notes.lua +++ b/notes.lua @@ -20,6 +20,8 @@ NotesOverlay.ATTRS{ function NotesOverlay:init() self.notes = {} + self.note_manager = nil + self.last_click_pos = {} self:reloadVisibleNotes() end @@ -27,27 +29,58 @@ function NotesOverlay:overlay_onupdate() self:reloadVisibleNotes() end -function NotesOverlay:overlay_trigger() - print('called') - -- self:reloadVisibleNotes() +function NotesOverlay:overlay_trigger(args) + return self:showNoteManager() end - function NotesOverlay:onInput(keys) if keys._MOUSE_L then local pos = dfhack.gui.getMousePos() - local notes = df.global.plotinfo.waypoints.points - for _, note in pairs(notes) do - if same_xyz(note.pos, pos) then - NoteManager{ - note=note, - on_update=function() self:reloadVisibleNotes() end - }:show() + local note = self:clickedNote(pos) + if note ~= nil then + self:showNoteManager(note) + end + end +end + +function NotesOverlay:clickedNote(click_pos) + local pos_curr_note = same_xyz(self.last_click_pos, click_pos) + and self.note_manager + and self.note_manager.note + or nil + + self.last_click_pos = click_pos + + local notes = df.global.plotinfo.waypoints.points + + local last_note_on_pos = nil + local first_note_on_pos = nil + for _, note in pairs(notes) do + if same_xyz(note.pos, click_pos) then + if last_note_on_pos == pos_curr_note then + return note end + + first_note_on_pos = first_note_on_pos or note + last_note_on_pos = note end + end + return first_note_on_pos +end + +function NotesOverlay:showNoteManager(note) + if self.note_manager ~= nil then + self.note_manager:dismiss() end + + self.note_manager = NoteManager{ + note=note, + on_update=function() self:reloadVisibleNotes() end + } + + return self.note_manager:show() end function NotesOverlay:viewportChanged() @@ -70,9 +103,9 @@ function NotesOverlay:onRenderFrame(dc) local texpos = dfhack.textures.getTexposByHandle(green_pin[1]) dc:pen({fg=COLOR_BLACK, bg=COLOR_LIGHTCYAN, tile=texpos}) - for _, point in pairs(self.notes) do + for _, note in pairs(self.notes) do dc - :seek(point.pos.x, point.pos.y) + :seek(note.screen_pos.x, note.screen_pos.y) :char('N') end @@ -80,7 +113,6 @@ function NotesOverlay:onRenderFrame(dc) end function NotesOverlay:reloadVisibleNotes() - print('reloading notes') self.notes = {} local viewport = guidm.Viewport.get() @@ -94,9 +126,8 @@ function NotesOverlay:reloadVisibleNotes() if viewport:isVisible(point.pos) then local pos = viewport:tileToScreen(point.pos) table.insert(self.notes, { - name=point.name, - comment=point.comment, - pos=pos + point=point, + screen_pos=pos }) end end @@ -261,6 +292,10 @@ function NoteManager:deleteNote() self:dismiss() end +function NoteManager:onDismiss() + self.note = nil +end + -- register widgets OVERLAY_WIDGETS = { map_notes=NotesOverlay @@ -278,7 +313,7 @@ local function main(args) return end - return NoteManager{}:show() + return dfhack.internal.runCommand('overlay trigger notes.map_notes') end end From b91876095d0cbd9910878043ad2a3c45a99dac63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Tue, 3 Sep 2024 18:55:39 +0200 Subject: [PATCH 05/15] Make text_editor mouse focus works only when click on its area --- internal/journal/text_editor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/journal/text_editor.lua b/internal/journal/text_editor.lua index 7b5a422c3..71521d580 100644 --- a/internal/journal/text_editor.lua +++ b/internal/journal/text_editor.lua @@ -234,7 +234,7 @@ function TextEditor:onInput(keys) return self.subviews.scrollbar:onInput(keys) end - if keys._MOUSE_L then + if keys._MOUSE_L and self:getMousePos() then self:setFocus(true) end From 5109ce562c00554e1289686a252b790d51b5c579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Tue, 3 Sep 2024 19:18:09 +0200 Subject: [PATCH 06/15] Polishing notes implementation --- notes.lua | 71 +++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/notes.lua b/notes.lua index 025cc2c81..feb710eba 100644 --- a/notes.lua +++ b/notes.lua @@ -15,9 +15,11 @@ NotesOverlay.ATTRS{ viewscreens='dwarfmode', default_enabled=true, overlay_onupdate_max_freq_seconds=30, - fullscreen=true, } +local waypoints = df.global.plotinfo.waypoints +local map_notes = df.global.plotinfo.waypoints.points + function NotesOverlay:init() self.notes = {} self.note_manager = nil @@ -35,11 +37,14 @@ end function NotesOverlay:onInput(keys) if keys._MOUSE_L then - local pos = dfhack.gui.getMousePos() + local top_most_screen = dfhack.gui.getDFViewscreen(true) + if dfhack.gui.matchFocusString('dwarfmode/Default', top_most_screen) then + local pos = dfhack.gui.getMousePos() - local note = self:clickedNote(pos) - if note ~= nil then - self:showNoteManager(note) + local note = self:clickedNote(pos) + if note ~= nil then + self:showNoteManager(note) + end end end end @@ -52,11 +57,9 @@ function NotesOverlay:clickedNote(click_pos) self.last_click_pos = click_pos - local notes = df.global.plotinfo.waypoints.points - local last_note_on_pos = nil local first_note_on_pos = nil - for _, note in pairs(notes) do + for _, note in ipairs(map_notes) do if same_xyz(note.pos, click_pos) then if last_note_on_pos == pos_curr_note then return note @@ -122,7 +125,7 @@ function NotesOverlay:reloadVisibleNotes() z=df.global.window_z } - for _, point in ipairs(df.global.plotinfo.waypoints.points) do + for _, point in ipairs(map_notes) do if viewport:isVisible(point.pos) then local pos = viewport:tileToScreen(point.pos) table.insert(self.notes, { @@ -135,8 +138,7 @@ end NoteManager = defclass(NoteManager, gui.ZScreen) NoteManager.ATTRS{ - focus_path='hotspot/menu', - hotspot_frame=DEFAULT_NIL, + focus_path='notes/note-manager', note=DEFAULT_NIL, on_update=DEFAULT_NIL, } @@ -148,7 +150,6 @@ function NoteManager:init() widgets.Window{ frame={w=35,h=20}, frame_inset={t=1}, - autoarrange_subviews=false, subviews={ widgets.HotkeyLabel { key='CUSTOM_ALT_N', @@ -158,50 +159,51 @@ function NoteManager:init() }, text_editor.TextEditor{ view_id='name', - focus_path='notes/name', frame={t=1,h=3}, frame_style=gui.FRAME_INTERIOR, - frame_style_b=nil, init_text=self.note and self.note.name or '' }, widgets.HotkeyLabel { key='CUSTOM_ALT_C', label='Comment', - frame={t=4}, + frame={t=5}, on_activate=function() self.subviews.comment:setFocus(true) end, }, text_editor.TextEditor{ view_id='comment', - frame={t=5,b=3}, - focus_path='notes/comment', + frame={t=6,b=3}, frame_style=gui.FRAME_INTERIOR, init_text=self.note and self.note.comment or '' }, widgets.Panel{ view_id='buttons', - frame={b=0,h=3}, + frame={b=0,h=2}, autoarrange_subviews=true, subviews={ - edit_mode and widgets.TextButton{ + widgets.HotkeyLabel{ view_id='Save', frame={h=1}, label='Save', key='CUSTOM_ALT_S', + visible=edit_mode, on_activate=function() self:saveNote() end, enabled=function() return #self.subviews.name:getText() > 0 end, - } or widgets.TextButton{ + }, + widgets.HotkeyLabel{ view_id='Create', frame={h=1}, label='Create', key='CUSTOM_ALT_S', + visible=not edit_mode, on_activate=function() self:createNote() end, enabled=function() return #self.subviews.name:getText() > 0 end, }, - edit_mode and widgets.TextButton{ + widgets.HotkeyLabel{ view_id='delete', frame={h=1}, label='Delete', key='CUSTOM_ALT_D', + visible=edit_mode, on_activate=function() self:deleteNote() end, } or nil, } @@ -212,8 +214,8 @@ function NoteManager:init() end function NoteManager:createNote() - local x, y, z = pos2xyz(df.global.cursor) - if x == nil then + local cursor_pos = guidm.getCursorPos() + if cursor_pos == nil then print('Enable keyboard cursor to add a note.') return end @@ -222,14 +224,12 @@ function NoteManager:createNote() local comment = self.subviews.comment:getText() if #name == 0 then - print('Note need at least a name') + dfhack.printerr('Note need at least a name') return end - local waypoints = df.global.plotinfo.waypoints - local notes = df.global.plotinfo.waypoints.points - notes:insert("#", { + map_notes:insert("#", { new=true, id = waypoints.next_point_id, @@ -238,7 +238,7 @@ function NoteManager:createNote() bg_color=0, name=name, comment=comment, - pos=xyz2pos(x, y, z) + pos=cursor_pos }) waypoints.next_point_id = waypoints.next_point_id + 1 @@ -258,11 +258,10 @@ function NoteManager:saveNote() local comment = self.subviews.comment:getText() if #name == 0 then - print('Note need at least a name') + dfhack.printerr('Note need at least a name') return end - local notes = df.global.plotinfo.waypoints.points self.note.name = name self.note.comment = comment @@ -278,9 +277,9 @@ function NoteManager:deleteNote() return end - for ind, note in pairs(df.global.plotinfo.waypoints.points) do - if note == self.note then - df.global.plotinfo.waypoints.points:erase(ind) + for ind, note in pairs(map_notes) do + if note.id == self.note.id then + map_notes:erase(ind) break end end @@ -307,9 +306,9 @@ local function main(args) end if args[1] == 'add' then - local x = pos2xyz(df.global.cursor) - if x == nil then - print('Enable keyboard cursor to add a note.') + local cursor_pos = guidm.getCursorPos() + if cursor_pos == nil then + dfhack.printerr('Enable keyboard cursor to add a note.') return end From bedd76d6dad7b6182b9c65e43f96c419fc17e844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Tue, 3 Sep 2024 19:59:18 +0200 Subject: [PATCH 07/15] Optimize notes to only scan notes on screen when clicked --- notes.lua | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/notes.lua b/notes.lua index feb710eba..2a0e3f95f 100644 --- a/notes.lua +++ b/notes.lua @@ -18,7 +18,7 @@ NotesOverlay.ATTRS{ } local waypoints = df.global.plotinfo.waypoints -local map_notes = df.global.plotinfo.waypoints.points +local map_points = df.global.plotinfo.waypoints.points function NotesOverlay:init() self.notes = {} @@ -59,9 +59,11 @@ function NotesOverlay:clickedNote(click_pos) local last_note_on_pos = nil local first_note_on_pos = nil - for _, note in ipairs(map_notes) do - if same_xyz(note.pos, click_pos) then - if last_note_on_pos == pos_curr_note then + for _, note in ipairs(self.notes) do + if same_xyz(note.point.pos, click_pos) then + if (last_note_on_pos and pos_curr_note + and last_note_on_pos.point.id == pos_curr_note.point.id + ) then return note end @@ -125,12 +127,12 @@ function NotesOverlay:reloadVisibleNotes() z=df.global.window_z } - for _, point in ipairs(map_notes) do - if viewport:isVisible(point.pos) then - local pos = viewport:tileToScreen(point.pos) + for _, map_point in ipairs(map_points) do + if viewport:isVisible(map_point.pos) then + local screen_pos = viewport:tileToScreen(map_point.pos) table.insert(self.notes, { - point=point, - screen_pos=pos + point=map_point, + screen_pos=screen_pos }) end end @@ -161,7 +163,7 @@ function NoteManager:init() view_id='name', frame={t=1,h=3}, frame_style=gui.FRAME_INTERIOR, - init_text=self.note and self.note.name or '' + init_text=self.note and self.note.point.name or '' }, widgets.HotkeyLabel { key='CUSTOM_ALT_C', @@ -173,7 +175,7 @@ function NoteManager:init() view_id='comment', frame={t=6,b=3}, frame_style=gui.FRAME_INTERIOR, - init_text=self.note and self.note.comment or '' + init_text=self.note and self.note.point.comment or '' }, widgets.Panel{ view_id='buttons', @@ -228,8 +230,7 @@ function NoteManager:createNote() return end - - map_notes:insert("#", { + map_points:insert("#", { new=true, id = waypoints.next_point_id, @@ -262,8 +263,8 @@ function NoteManager:saveNote() return end - self.note.name = name - self.note.comment = comment + self.note.point.name = name + self.note.point.comment = comment if self.on_update then self.on_update() @@ -277,9 +278,9 @@ function NoteManager:deleteNote() return end - for ind, note in pairs(map_notes) do - if note.id == self.note.id then - map_notes:erase(ind) + for ind, map_point in pairs(map_points) do + if map_point.id == self.note.point.id then + map_points:erase(ind) break end end From 7a7c6eb01c62be5c52988a4e124ed0236087605f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Tue, 3 Sep 2024 20:41:55 +0200 Subject: [PATCH 08/15] Make notes modal cursor starts at the begining of the edited note text --- notes.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/notes.lua b/notes.lua index 2a0e3f95f..c1705e915 100644 --- a/notes.lua +++ b/notes.lua @@ -163,7 +163,8 @@ function NoteManager:init() view_id='name', frame={t=1,h=3}, frame_style=gui.FRAME_INTERIOR, - init_text=self.note and self.note.point.name or '' + init_text=self.note and self.note.point.name or '', + init_cursor=1 }, widgets.HotkeyLabel { key='CUSTOM_ALT_C', @@ -175,7 +176,8 @@ function NoteManager:init() view_id='comment', frame={t=6,b=3}, frame_style=gui.FRAME_INTERIOR, - init_text=self.note and self.note.point.comment or '' + init_text=self.note and self.note.point.comment or '', + init_cursor=1 }, widgets.Panel{ view_id='buttons', From 02e193318f3dfe1bac989e232c378b96e1cff272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Wed, 4 Sep 2024 06:54:46 +0200 Subject: [PATCH 09/15] Improve new/edit note modal design --- notes.lua | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/notes.lua b/notes.lua index c1705e915..b06abcf04 100644 --- a/notes.lua +++ b/notes.lua @@ -21,7 +21,7 @@ local waypoints = df.global.plotinfo.waypoints local map_points = df.global.plotinfo.waypoints.points function NotesOverlay:init() - self.notes = {} + self.visible_notes = {} self.note_manager = nil self.last_click_pos = {} self:reloadVisibleNotes() @@ -40,6 +40,9 @@ function NotesOverlay:onInput(keys) local top_most_screen = dfhack.gui.getDFViewscreen(true) if dfhack.gui.matchFocusString('dwarfmode/Default', top_most_screen) then local pos = dfhack.gui.getMousePos() + if pos == nil then + return false + end local note = self:clickedNote(pos) if note ~= nil then @@ -59,7 +62,7 @@ function NotesOverlay:clickedNote(click_pos) local last_note_on_pos = nil local first_note_on_pos = nil - for _, note in ipairs(self.notes) do + for _, note in ipairs(self.visible_notes) do if same_xyz(note.point.pos, click_pos) then if (last_note_on_pos and pos_curr_note and last_note_on_pos.point.id == pos_curr_note.point.id @@ -108,7 +111,7 @@ function NotesOverlay:onRenderFrame(dc) local texpos = dfhack.textures.getTexposByHandle(green_pin[1]) dc:pen({fg=COLOR_BLACK, bg=COLOR_LIGHTCYAN, tile=texpos}) - for _, note in pairs(self.notes) do + for _, note in pairs(self.visible_notes) do dc :seek(note.screen_pos.x, note.screen_pos.y) :char('N') @@ -118,7 +121,7 @@ function NotesOverlay:onRenderFrame(dc) end function NotesOverlay:reloadVisibleNotes() - self.notes = {} + self.visible_notes = {} local viewport = guidm.Viewport.get() self.viewport_pos = { @@ -130,7 +133,7 @@ function NotesOverlay:reloadVisibleNotes() for _, map_point in ipairs(map_points) do if viewport:isVisible(map_point.pos) then local screen_pos = viewport:tileToScreen(map_point.pos) - table.insert(self.notes, { + table.insert(self.visible_notes, { point=map_point, screen_pos=screen_pos }) @@ -152,11 +155,13 @@ function NoteManager:init() widgets.Window{ frame={w=35,h=20}, frame_inset={t=1}, + resizable=true, subviews={ widgets.HotkeyLabel { key='CUSTOM_ALT_N', label='Name', - frame={t=0}, + frame={l=0,t=0}, + auto_width=true, on_activate=function() self.subviews.name:setFocus(true) end, }, text_editor.TextEditor{ @@ -169,7 +174,8 @@ function NoteManager:init() widgets.HotkeyLabel { key='CUSTOM_ALT_C', label='Comment', - frame={t=5}, + frame={l=0,t=5}, + auto_width=true, on_activate=function() self.subviews.comment:setFocus(true) end, }, text_editor.TextEditor{ @@ -181,12 +187,13 @@ function NoteManager:init() }, widgets.Panel{ view_id='buttons', - frame={b=0,h=2}, - autoarrange_subviews=true, + frame={b=0,h=1}, + frame_inset={l=1,r=1}, subviews={ widgets.HotkeyLabel{ view_id='Save', - frame={h=1}, + frame={l=0,t=0,h=1}, + auto_width=true, label='Save', key='CUSTOM_ALT_S', visible=edit_mode, @@ -195,7 +202,8 @@ function NoteManager:init() }, widgets.HotkeyLabel{ view_id='Create', - frame={h=1}, + frame={l=0,t=0,h=1}, + auto_width=true, label='Create', key='CUSTOM_ALT_S', visible=not edit_mode, @@ -204,7 +212,8 @@ function NoteManager:init() }, widgets.HotkeyLabel{ view_id='delete', - frame={h=1}, + frame={r=0,t=0,h=1}, + auto_width=true, label='Delete', key='CUSTOM_ALT_D', visible=edit_mode, @@ -220,7 +229,7 @@ end function NoteManager:createNote() local cursor_pos = guidm.getCursorPos() if cursor_pos == nil then - print('Enable keyboard cursor to add a note.') + dfhack.printerr('Enable keyboard cursor to add a note.') return end From 37fcaa1073c51601fc81b1bf08bde7b9b71e1b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Wed, 4 Sep 2024 07:04:34 +0200 Subject: [PATCH 10/15] Hide notes that have no "name" (most likely they are waypoints) --- notes.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/notes.lua b/notes.lua index b06abcf04..90e50b861 100644 --- a/notes.lua +++ b/notes.lua @@ -131,7 +131,9 @@ function NotesOverlay:reloadVisibleNotes() } for _, map_point in ipairs(map_points) do - if viewport:isVisible(map_point.pos) then + if (viewport:isVisible(map_point.pos) + and map_point.name ~= nil and #map_point.name > 0) + then local screen_pos = viewport:tileToScreen(map_point.pos) table.insert(self.visible_notes, { point=map_point, From 0da7101cf8a3e00b24d93a24d587d49a6f9d500a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Wed, 4 Sep 2024 17:53:33 +0200 Subject: [PATCH 11/15] Move gui/journal help screen from text_editor to actual journal script --- gui/journal.lua | 1 + internal/journal/text_editor.lua | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/journal.lua b/gui/journal.lua index cfb6f234b..d14468129 100644 --- a/gui/journal.lua +++ b/gui/journal.lua @@ -123,6 +123,7 @@ function JournalWindow:init() on_text_change=self:callback('onTextChange'), on_cursor_change=self:callback('onCursorChange'), }, + widgets.HelpButton{command="gui/journal", frame={r=0,t=1}}, widgets.Panel{ frame={l=0,r=0,b=1,h=1}, frame_inset={l=1,r=1,t=0, w=100}, diff --git a/internal/journal/text_editor.lua b/internal/journal/text_editor.lua index 71521d580..a5f27c3af 100644 --- a/internal/journal/text_editor.lua +++ b/internal/journal/text_editor.lua @@ -123,8 +123,7 @@ function TextEditor:init() view_id='scrollbar', frame={r=0,t=1}, on_scroll=self:callback('onScrollbar') - }, - widgets.HelpButton{command="gui/journal", frame={r=0,t=0}} + } } self:setFocus(true) end From 7fd812ccbf354a00e52cc3251235154a63f1bf31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Wed, 4 Sep 2024 18:18:44 +0200 Subject: [PATCH 12/15] Add one line mode to TextEditor and use it in notes --- internal/journal/text_editor.lua | 46 +++++++++++++++++++++----------- notes.lua | 10 +++++-- test/gui/journal.lua | 5 +++- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/internal/journal/text_editor.lua b/internal/journal/text_editor.lua index a5f27c3af..cf15776d4 100644 --- a/internal/journal/text_editor.lua +++ b/internal/journal/text_editor.lua @@ -94,6 +94,7 @@ TextEditor.ATTRS{ select_pen = COLOR_CYAN, on_text_change = DEFAULT_NIL, on_cursor_change = DEFAULT_NIL, + one_line_mode = false, debug = false } @@ -104,25 +105,27 @@ function TextEditor:init() TextEditorView{ view_id='text_area', frame={l=0,r=3,t=0}, - text = self.init_text, + text=self.init_text, - text_pen = self.text_pen, - ignore_keys = self.ignore_keys, - select_pen = self.select_pen, - debug = self.debug, + text_pen=self.text_pen, + ignore_keys=self.ignore_keys, + select_pen=self.select_pen, + debug=self.debug, + one_line_mode=self.one_line_mode, - on_text_change = function (val) + on_text_change=function (val) self:updateLayout() if self.on_text_change then self.on_text_change(val) end end, - on_cursor_change = self:callback('onCursorChange') + on_cursor_change=self:callback('onCursorChange') }, widgets.Scrollbar{ view_id='scrollbar', frame={r=0,t=1}, - on_scroll=self:callback('onScrollbar') + on_scroll=self:callback('onScrollbar'), + visible=not self.one_line_mode } } self:setFocus(true) @@ -251,6 +254,7 @@ TextEditorView.ATTRS{ on_cursor_change = DEFAULT_NIL, enable_cursor_blink = true, debug = false, + one_line_mode = false, history_size = 10, } @@ -273,6 +277,8 @@ function TextEditorView:init() bold=true }) + self.text = self:normalizeText(self.text) + self.wrapped_text = wrapped_text.WrappedText{ text=self.text, wrap_width=256 @@ -281,6 +287,14 @@ function TextEditorView:init() self.history = TextEditorHistory{history_size=self.history_size} end +function TextEditorView:normalizeText(text) + if self.one_line_mode then + return text:gsub("\r?\n", "") + end + + return text +end + function TextEditorView:setRenderStartLineY(render_start_line_y) self.render_start_line_y = render_start_line_y end @@ -410,7 +424,7 @@ end function TextEditorView:setText(text) local changed = self.text ~= text - self.text = text + self.text = self:normalizeText(text) self:recomputeLines() @@ -782,12 +796,14 @@ end function TextEditorView:onTextManipulationInput(keys) if keys.SELECT then -- handle enter - self.history:store( - HISTORY_ENTRY.WHITESPACE_BLOCK, - self.text, - self.cursor - ) - self:insert(NEWLINE) + if not self.one_line_mode then + self.history:store( + HISTORY_ENTRY.WHITESPACE_BLOCK, + self.text, + self.cursor + ) + self:insert(NEWLINE) + end return true diff --git a/notes.lua b/notes.lua index 90e50b861..b025f0185 100644 --- a/notes.lua +++ b/notes.lua @@ -7,7 +7,12 @@ local overlay = require('plugins.overlay') local guidm = require('gui.dwarfmode') local text_editor = reqscript('internal/journal/text_editor') -local green_pin = dfhack.textures.loadTileset('hack/data/art/note-green-pin.png', 32, 32, true) +local green_pin = dfhack.textures.loadTileset( + 'hack/data/art/note_green_pin_map.png', + 32, + 32, + true +) NotesOverlay = defclass(NotesOverlay, overlay.OverlayWidget) NotesOverlay.ATTRS{ @@ -171,7 +176,8 @@ function NoteManager:init() frame={t=1,h=3}, frame_style=gui.FRAME_INTERIOR, init_text=self.note and self.note.point.name or '', - init_cursor=1 + init_cursor=1, + one_line_mode=true }, widgets.HotkeyLabel { key='CUSTOM_ALT_C', diff --git a/test/gui/journal.lua b/test/gui/journal.lua index 29fcc1d69..d845aef20 100644 --- a/test/gui/journal.lua +++ b/test/gui/journal.lua @@ -77,7 +77,7 @@ local function arrange_empty_journal(options) gui_journal.main({ save_prefix='test:', save_on_change=options.save_on_change or false, - save_layout=options.allow_layout_restore or false + save_layout=options.allow_layout_restore or false, }) local journal = gui_journal.view @@ -3068,3 +3068,6 @@ function test.show_tutorials_on_first_use() expect.str_find('Section 1\n', read_rendered_text(toc_panel)); journal:dismiss() end + +-- TODO: separate journal tests from TextEditor tests +-- add "one_line_mode" tests From 30feadb4689353a0c66e62caf445f34f75f9456e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Wed, 4 Sep 2024 19:21:31 +0200 Subject: [PATCH 13/15] Add basic documentation for notes tool --- docs/notes.rst | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/notes.rst diff --git a/docs/notes.rst b/docs/notes.rst new file mode 100644 index 000000000..3bfc5304e --- /dev/null +++ b/docs/notes.rst @@ -0,0 +1,42 @@ +notes +===== + +.. dfhack-tool:: + :summary: Manage map-specific notes + :tags: fort interface map + +The `notes` tool enables players to annotate specific tiles +on the Dwarf Fortress game map with customizable notes. + +Each note is displayed as a green pin on the map and includes a one-line title and a detailed comment. + +It can be used to e.g.: + - marking plans for future constructions + - explaining mechanisms or traps + - noting historical events + +Usage +----- + +:: + + notes add + +Add new note in the current position of the keyboard cursor. + +Creating a Note +~~~~~~~~~~~~~~~ +1. Use the keyboard cursor to select the desired map tile where you want to place a note. +2. Execute ``notes add`` via the DFHack console. +3. In the pop-up dialog, fill in the note's title and detailed comment. +4. Press :kbd:`Alt` + :kbd:`S` to create the note. + +Editing or Deleting a Note +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- Click on the green pin representing the note directly on the map. +- A dialog will appear, offering options to edit the title or comment, or to delete the note entirely. + +Managing Notes Visibility +~~~~~~~~~~~~~~~~~~~~~~~~~ +- Access the ``DFHack Control Panel`` / ``UI Overlays`` tab. +- Toggle the ``notes.map-notes`` overlay to show or hide the notes on the map. From 0f5ab80dcceec6b074346000c0d811e1e2797c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Wed, 4 Sep 2024 19:24:34 +0200 Subject: [PATCH 14/15] Add notes tool to changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 5e1761acb..2cee1f410 100644 --- a/changelog.txt +++ b/changelog.txt @@ -30,6 +30,7 @@ Template for new versions: - `embark-anyone`: allows you to embark as any civilization, including dead and non-dwarven ones - `idle-crafting`: allow dwarves to independently satisfy their need to craft objects - `gui/family-affairs`: (reinstated) inspect or meddle with pregnancies, marriages, or lover relationships +- `notes`: Manage map-specific notes ## New Features - `caravan`: DFHack dialogs for trade screens (both ``Bring goods to depot`` and the ``Trade`` barter screen) can now filter by item origins (foreign vs. fort-made) and can filter bins by whether they have a mix of ethically acceptable and unacceptable items in them From cc223f1991b00f0e9f2395a9b7b412a612c1eec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Obr=C4=99bski?= Date: Thu, 5 Sep 2024 08:09:17 +0200 Subject: [PATCH 15/15] Polishing notes documentation --- docs/notes.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/notes.rst b/docs/notes.rst index 3bfc5304e..faed2c8fb 100644 --- a/docs/notes.rst +++ b/docs/notes.rst @@ -2,7 +2,7 @@ notes ===== .. dfhack-tool:: - :summary: Manage map-specific notes + :summary: Manage map-specific notes. :tags: fort interface map The `notes` tool enables players to annotate specific tiles @@ -25,18 +25,18 @@ Usage Add new note in the current position of the keyboard cursor. Creating a Note -~~~~~~~~~~~~~~~ +--------------- 1. Use the keyboard cursor to select the desired map tile where you want to place a note. 2. Execute ``notes add`` via the DFHack console. 3. In the pop-up dialog, fill in the note's title and detailed comment. 4. Press :kbd:`Alt` + :kbd:`S` to create the note. Editing or Deleting a Note -~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------- - Click on the green pin representing the note directly on the map. - A dialog will appear, offering options to edit the title or comment, or to delete the note entirely. Managing Notes Visibility -~~~~~~~~~~~~~~~~~~~~~~~~~ -- Access the ``DFHack Control Panel`` / ``UI Overlays`` tab. +------------------------- +- Access the `gui/control-panel` / ``UI Overlays`` tab. - Toggle the ``notes.map-notes`` overlay to show or hide the notes on the map.