Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

notes script #1287

Merged
merged 15 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions internal/journal/table_of_contents.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
10 changes: 8 additions & 2 deletions internal/journal/text_editor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -234,6 +234,10 @@ function TextEditor:onInput(keys)
return self.subviews.scrollbar:onInput(keys)
end

if keys._MOUSE_L then
myk002 marked this conversation as resolved.
Show resolved Hide resolved
self:setFocus(true)
end

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

Expand Down Expand Up @@ -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

Expand Down
322 changes: 322 additions & 0 deletions notes.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
--@ 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/note-green-pin.png', 32, 32, true)

NotesOverlay = defclass(NotesOverlay, overlay.OverlayWidget)
NotesOverlay.ATTRS{
desc='Render map notes.',
viewscreens='dwarfmode',
default_enabled=true,
overlay_onupdate_max_freq_seconds=30,
fullscreen=true,
myk002 marked this conversation as resolved.
Show resolved Hide resolved
}

function NotesOverlay:init()
self.notes = {}
self.note_manager = nil
self.last_click_pos = {}
self:reloadVisibleNotes()
end

function NotesOverlay:overlay_onupdate()
self:reloadVisibleNotes()
end

function NotesOverlay:overlay_trigger(args)
return self:showNoteManager()
end

function NotesOverlay:onInput(keys)
if keys._MOUSE_L then
local pos = dfhack.gui.getMousePos()
myk002 marked this conversation as resolved.
Show resolved Hide resolved

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)
myk002 marked this conversation as resolved.
Show resolved Hide resolved
and self.note_manager
and self.note_manager.note
or nil

self.last_click_pos = click_pos

local notes = df.global.plotinfo.waypoints.points
myk002 marked this conversation as resolved.
Show resolved Hide resolved

local last_note_on_pos = nil
local first_note_on_pos = nil
for _, note in pairs(notes) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complexity of this loop (which should be using ipairs, btw) will grow with the number of notes/squad waypoints that are defined. would it be a good idea to cache them in a nested map structure so you can retrieve a note via safe_index(self.notes_cache, pos.z, pos.y, pos.x)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I optimized it now in different way - I scan only notes that are visible (and cached before). What do you think?

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()
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])
dc:pen({fg=COLOR_BLACK, bg=COLOR_LIGHTCYAN, tile=texpos})

for _, note in pairs(self.notes) do
dc
:seek(note.screen_pos.x, note.screen_pos.y)
:char('N')
end

dc:map(false)
end

function NotesOverlay:reloadVisibleNotes()
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)
table.insert(self.notes, {
point=point,
screen_pos=pos
})
end
end
end

NoteManager = defclass(NoteManager, gui.ZScreen)
NoteManager.ATTRS{
focus_path='hotspot/menu',
myk002 marked this conversation as resolved.
Show resolved Hide resolved
hotspot_frame=DEFAULT_NIL,
myk002 marked this conversation as resolved.
Show resolved Hide resolved
note=DEFAULT_NIL,
on_update=DEFAULT_NIL,
}

function NoteManager:init()
local edit_mode = self.note ~= nil

self:addviews{
widgets.Window{
frame={w=35,h=20},
frame_inset={t=1},
autoarrange_subviews=false,
myk002 marked this conversation as resolved.
Show resolved Hide resolved
subviews={
widgets.HotkeyLabel {
myk002 marked this conversation as resolved.
Show resolved Hide resolved
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',
myk002 marked this conversation as resolved.
Show resolved Hide resolved
frame={t=1,h=3},
frame_style=gui.FRAME_INTERIOR,
frame_style_b=nil,
myk002 marked this conversation as resolved.
Show resolved Hide resolved
init_text=self.note and self.note.name or ''
},
widgets.HotkeyLabel {
myk002 marked this conversation as resolved.
Show resolved Hide resolved
key='CUSTOM_ALT_C',
label='Comment',
frame={t=4},
myk002 marked this conversation as resolved.
Show resolved Hide resolved
on_activate=function() self.subviews.comment:setFocus(true) end,
},
text_editor.TextEditor{
view_id='comment',
frame={t=5,b=3},
focus_path='notes/comment',
myk002 marked this conversation as resolved.
Show resolved Hide resolved
frame_style=gui.FRAME_INTERIOR,
init_text=self.note and self.note.comment or ''
},
widgets.Panel{
view_id='buttons',
frame={b=0,h=3},
myk002 marked this conversation as resolved.
Show resolved Hide resolved
autoarrange_subviews=true,
subviews={
edit_mode and widgets.TextButton{
myk002 marked this conversation as resolved.
Show resolved Hide resolved
myk002 marked this conversation as resolved.
Show resolved Hide resolved
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',
key='CUSTOM_ALT_S',
on_activate=function() self:createNote() end,
enabled=function() return #self.subviews.name:getText() > 0 end,
},
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,
}
}
},
},
}
end

function NoteManager:createNote()
local x, y, z = pos2xyz(df.global.cursor)
if x == nil then
myk002 marked this conversation as resolved.
Show resolved Hide resolved
print('Enable keyboard cursor to add a note.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A mouse-capable way of adding a note is generally expected of current tools. See gui/blueprint for a possible example. You click on a button and then click on the map. while the location is being selected, a marker is rendered under the mouse cursor: https://github.com/DFHack/scripts/blob/master/gui/blueprint.lua#L427

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems very good, but for now we do not have any notes interface where such button can be placed.
Do you think I should replace notes add command by mouse cursor selection?
Or this should stay as it is and the new way should appear after there will be a gui/notes for managing notes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a gui/notes entrypoint, but I also think that this dialog should have a way to create (and switch to editing) an additional note

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be confusing.

  1. You clicked on a note to edit it and in a dialog you have option "New note"? Did you current changes to note saved or not? Do it switch to new note overlay mouse hover mode? etc.

  2. You creating a new note, and got to moment when you see this dialog. Then you switch to editing. What this really means? You are forced to select a note on the screen now? Its sounds very confusing.
    If you want to switch to editing - just select a note on the map. You can do this right now, do not need any additional wat to "switch" to it.
    --
    I never met such features in my web/desktop experience. Edit dialog is for editing, new dialog is for creating.

I believe the needs you speak about will be fully solved by gui/notes tool.
I though I can create it later, but maybe it should be done from the begining to avoid the confusion.

myk002 marked this conversation as resolved.
Show resolved Hide resolved
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')
myk002 marked this conversation as resolved.
Show resolved Hide resolved
return
end

local waypoints = df.global.plotinfo.waypoints
local notes = df.global.plotinfo.waypoints.points

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

if self.on_update then
self.on_update()
end

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

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
myk002 marked this conversation as resolved.
Show resolved Hide resolved
df.global.plotinfo.waypoints.points:erase(ind)
break
end
end

if self.on_update then
self.on_update()
end

self:dismiss()
end

function NoteManager:onDismiss()
self.note = nil
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
myk002 marked this conversation as resolved.
Show resolved Hide resolved
print('Enable keyboard cursor to add a note.')
return
end

return dfhack.internal.runCommand('overlay trigger notes.map_notes')
end
end

if not dfhack_flags.module then
main({...})
end
Loading