diff --git a/src/common.nim b/src/common.nim index d00c94a4..9e63b795 100644 --- a/src/common.nim +++ b/src/common.nim @@ -352,6 +352,29 @@ func `<`*(a, b: Location): bool = else: false +type + NotesListFilter* = object + scope*: NoteScopeFilter + noteType*: set[NoteTypeFilter] + searchTerm*: string + orderBy*: NoteOrdering + + NoteScopeFilter* = enum + nsfMap = "Map" + nsfLevel = "Level" + nsfRegion = "Region" + + NoteTypeFilter* = enum + ntfNone = ("None") + ntfNumber = ("Num") + ntfId = ("ID") + ntfIcon = ("Icon") + + NoteOrdering* = enum + noType = "Type" + noText = "Text" + + type WindowTheme* = ref object borderColor*: Color diff --git a/src/main.nim b/src/main.nim index 93a25874..16ce40d1 100644 --- a/src/main.nim +++ b/src/main.nim @@ -516,7 +516,6 @@ type prevFilter: NotesListFilter linkCursor: bool prevLinkCursor: bool - grouping: int cache: seq[NotesListCacheEntry] sectionStates: Table[int, bool] regionStates: Table[tuple[levelId: Natural, rc: RegionCoords], bool] @@ -536,27 +535,6 @@ type of nckRegion: regionCoords: RegionCoords - NotesListFilter = object - scope: NoteScopeFilter - noteType: seq[NoteTypeFilter] - searchTerm: string - ordering: NoteOrdering - - NoteScopeFilter = enum - nsfMap = "Map" - nsfLevel = "Level" - nsfRegion = "Region" - - NoteTypeFilter = enum - ntfNone = "None" - ntfNumber = "Num" - ntfId = "ID" - ntfIcon = "Icon" - - NoteOrdering = enum - noType = "Type" - noText = "Text" - StatusMessage = object icon: string @@ -3048,6 +3026,7 @@ proc loadMap(path: string; a): bool = # {{{ saveMap() proc saveMap(path: string, autosave, createBackup: bool; a) = alias(dp, a.ui.drawLevelParams) + alias(nls, a.ui.notesListState) let cur = a.ui.cursor @@ -3070,6 +3049,13 @@ proc saveMap(path: string, autosave, createBackup: bool; a) = currFloorColor: a.ui.currFloorColor, currSpecialWall: a.ui.currSpecialWall, + + notesListPaneState: AppStateNotesListPane( + filter: nls.currFilter, + linkCursor: nls.linkCursor, + sectionStates: nls.sectionStates, + regionStates: nls.regionStates + ) ) log.info(fmt"Saving map to '{path}'") @@ -8266,7 +8252,7 @@ proc rebuildNotesListCache(textW: float; a) = # Rebuild/reset caches if needed const NoteVertPad = 18 - proc toAnnotationKindSet(filter: seq[NoteTypeFilter]): set[AnnotationKind] = + proc toAnnotationKindSet(filter: set[NoteTypeFilter]): set[AnnotationKind] = for f in filter: case f of ntfNone: result.incl(akComment) @@ -8296,8 +8282,8 @@ proc rebuildNotesListCache(textW: float; a) = ) proc sortCacheEntries(s: var seq[NotesListCacheEntry], - ordering: NoteOrdering) = - case ordering + orderBy: NoteOrdering) = + case orderBy of noType: s.sort(sortByTypeTextLocation) of noText: s.sort(sortByTextLocationType) @@ -8312,7 +8298,7 @@ proc rebuildNotesListCache(textW: float; a) = Location(levelId: l.id, row: r, col: c), note, vg ) - sortCacheEntries(s, nls.currFilter.ordering) + sortCacheEntries(s, nls.currFilter.orderBy) s proc collectRegionNotes(l: Level, rc: RegionCoords; a): auto = @@ -8322,7 +8308,7 @@ proc rebuildNotesListCache(textW: float; a) = Location(levelId: l.id, row: loc.row, col: loc.col), note, vg ) - sortCacheEntries(s, nls.currFilter.ordering) + sortCacheEntries(s, nls.currFilter.orderBy) s proc collectAllRegionNotes(l: Level; a): seq[NotesListCacheEntry] = @@ -8473,7 +8459,7 @@ proc renderNotesListPane(x, y, w, h: float; a) = if koi.button(wx+245, wy, w=ButtonWidth, wh, "A", tooltip = "Show all note types", style = a.theme.buttonStyle): - nls.currFilter.noteType = @[ntfNone, ntfNumber, ntfId, ntfIcon] + nls.currFilter.noteType = {ntfNone, ntfNumber, ntfId, ntfIcon} koi.multiRadioButtons( wx, wy, w=w-LeftPad-RightPad - 30, wh, @@ -8492,6 +8478,11 @@ proc renderNotesListPane(x, y, w, h: float; a) = koi.textField( wx+64, wy, w=174, wh, nls.currFilter.searchTerm, + constraint = TextFieldConstraint( + kind: tckString, + minLen: NotesListSearchTermLimits.minRuneLen, + maxLen: NotesListSearchTermLimits.maxRuneLen.some + ).some, style = a.theme.textFieldStyle ) @@ -8501,7 +8492,7 @@ proc renderNotesListPane(x, y, w, h: float; a) = koi.label(wx+1, wy, 60, wh, "Order by", style=a.theme.labelStyle) koi.dropDown( - wx+64, wy, w=65, wh, nls.currFilter.ordering, + wx+64, wy, w=65, wh, nls.currFilter.orderBy, style = a.theme.dropDownStyle ) @@ -10205,7 +10196,7 @@ proc initApp(configFile: Option[string], mapFile: Option[string], # TODO init from config with a.ui.notesListState: currFilter.noteType = @[ntfNone, ntfNumber, ntfId, ntfIcon] - currFilter.ordering = noType + currFilter.orderBy = noType loadFonts(a) loadAndSetIcon(a) diff --git a/src/persistence.nim b/src/persistence.nim index 03599207..6d0c2030 100644 --- a/src/persistence.nim +++ b/src/persistence.nim @@ -88,6 +88,8 @@ const ZoomLevelLimits* = intLimits(min=MinZoomLevel, max=MaxZoomLevel) SpecialWallLimits* = intLimits(min=0, max=SpecialWalls.high) + NotesListSearchTermLimits* = strLimits(minRuneLen=0, maxRuneLen=100) + # }}} # {{{ Types type @@ -98,7 +100,13 @@ type ctRunLengthEncoded = 1 ctZeroes = 2 - AppState* = object + AppStateNotesListPane* = ref object + filter*: NotesListFilter + linkCursor*: bool + sectionStates*: Table[int, bool] + regionStates*: Table[tuple[levelId: Natural, rc: RegionCoords], bool] + + AppState* = ref object themeName*: string zoomLevel*: range[MinZoomLevel..MaxZoomLevel] @@ -118,6 +126,8 @@ type currFloorColor*: range[0..LevelTheme.floorBackgroundColor.high] currSpecialWall*: range[0..SpecialWalls.high] + notesListPaneState*: AppStateNotesListPane + # }}} # {{{ Chunk IDs const @@ -335,6 +345,10 @@ proc readAppState_preV4(rr; map: Map): AppState = optWasdMode: optWasdMode.bool, optWalkMode: optWalkMode.bool, + # TODO +# notesListSectionStates: +# notesListRegionStates: + currFloorColor: currFloorColor, currSpecialWall: currSpecialWall ) @@ -465,7 +479,9 @@ proc readAppState_V4(rr; map: Map): AppState = app.currSpecialWall = currSpecialWall # Note list pane state -# if toolCursor.isSome: + if notlCursor.isSome: + # TODO + discard result = app @@ -1155,6 +1171,32 @@ using rw: RiffWriter var g_runLengthEncoder: RunLengthEncoder +# {{{ writeNotesListPaneState() +proc writeNotesListPaneState(rw; map: Map, s: AppState) = + alias(nls, s.notesListPaneState) + + rw.write(nls.filter.scope.uint8) +# rw.write(nls.filter.noteType.uint8) + rw.writeWStr(nls.filter.searchTerm) + rw.write(nls.filter.orderBy.uint8) + rw.write(nls.linkCursor.uint8) + + rw.chunk(FourCC_GRMM_notl): + for levelId in map.sortedLevelIds: + rw.write(nls.sectionStates[levelId].uint8) + + let l = map.levels[levelId] + + # `numRegions` can be non-zero even if `regionOpts.enabled` is `false`. + if l.regions.numRegions > 0: + + # Iterate through the regions starting from region coords (0,0) + # (top-left corner), then go left to right, top to bottom. + for rc in l.regionCoords: + let r = l.regions[rc].get + rw.write(nls.regionStates[(levelId, rc)].uint8) + +# }}} # {{{ writeAppState() proc writeAppState(rw; map: Map, s: AppState) = rw.listChunk(FourCC_GRMM_stat): @@ -1187,7 +1229,7 @@ proc writeAppState(rw; map: Map, s: AppState) = rw.write(s.currSpecialWall.uint8) # Notes list pane state -# rw.chunk(FourCC_GRMM_notl): + writeNotesListPaneState(rw, map, s) # }}} # {{{ writeLinks() @@ -1200,8 +1242,8 @@ proc writeLinks(rw; map: Map) = # sortedLevelIds, the indices will point to the correct levels. # # When loading the levels back, we assign the the first level ID 0, the - # second ID 1, etc. (their indices in the map file) to ensure the level IDs - # are in sync with the links. + # second ID 1, etc. (their indices as they appear in the map file) to + # ensure the level IDs are in sync with the links. let levelIdToIndex = collect: for idx, id in map.sortedLevelIds: {id: idx}