Skip to content

Commit

Permalink
Add SearchEntry widget
Browse files Browse the repository at this point in the history
* Add Search Entry Widget

* Add text field to searchEntry widget

* Add missing text hook

* Improve search entry example

* Further improve example

* Refine which fields you shouldn't have access to

* Add activity to when you stop a search

* Add activity to when you stop a search

* Fix search entry displaying weird spacing

* Unify GtkMinor into a single constant

* Debug change

Attempt to cat out the gtk.nim file to see how the hell it is
getting the impression that GtkMinor is defined twice.

* Move GtkMInor before the passL flag is passed?

Maybe this fixes the problem with the pipeline sudenly thinking
that value was defined twice.

* Unto test-pipeline debug change

* Comment out unsupported search thingy

* Minor tweaks

- Removed searchstring parameter from callbacks
- made sure only changed callback can modify search value
- Updated example
- Moved example to far nicer looking ListBox

* Remove unnecessary Box

* Update examples/widgets/search_entry.nim

Co-authored-by: Can Lehmann <85876381+can-lehmann@users.noreply.github.com>

---------

Co-authored-by: Can Lehmann <85876381+can-lehmann@users.noreply.github.com>
  • Loading branch information
PhilippMDoerner and can-lehmann authored Oct 17, 2023
1 parent 4cf8a3e commit 0ffacf8
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 3 deletions.
Binary file added docs/assets/examples/search_entry.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions docs/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,29 @@ PopoverMenu:
```


## SearchEntry

```nim
renderable SearchEntry of BaseWidget
```

###### Fields

- All fields from [BaseWidget](#BaseWidget)
- `text: string`
- `searchDelay: uint = 100` Determines the minimum time after a `searchChanged` event occurred before the next can be emitted. Only available when compiling for gtk 4.8
- `placeholderText: string = "Search"` Only available when compiling for gtk 4.10

###### Events

- activate: `proc (searchString: string)` Triggered when the user "activated" the search e.g. by hitting "enter" key while SearchEntry is in focus.
- nextMatch: `proc ()` Triggered when the user hits the "next entry" keybinding while the search entry is in focus, which is Ctrl-g by default.
- previousMatch: `proc ()` Triggered when the user hits the "previous entry" keybinding while the search entry is in focus, which is Ctrl-Shift-g by default.
- changed: `proc (searchString: string)` Triggered when the user types in the SearchEntry.
- searchStarted: `proc (searchString: string)`
- stopSearch: `proc (searchString: string)` Triggered when the user "stops" a search, e.g. by hitting the "Esc" key while SearchEntry is in focus.


## Separator

```nim
Expand Down
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ The `widgets` directory contains examples for how to use different widgets.
<td><a href="https://github.com/can-lehmann/owlkettle/blob/main/examples/widgets/scale.nim">Scale</a></td>
<td><img alt="Scale Widget" src="../docs/assets/examples/scale.png" width="922px"></td>
</tr>
<tr>
<td><a href="https://github.com/can-lehmann/owlkettle/blob/main/examples/widgets/search_entry.nim">Search Entry</a></td>
<td><img alt="Search Entry Widget" src="../docs/assets/examples/search_entry.png" width="422px"></td>
</tr>
<tr>
<td><a href="https://github.com/can-lehmann/owlkettle/blob/main/examples/widgets/text_view.nim">Text View</a></td>
<td><img alt="Text View" src="../docs/assets/examples/text_view.png" width="757px"></td>
Expand Down
90 changes: 90 additions & 0 deletions examples/widgets/search_entry.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# MIT License
#
# Copyright (c) 2023 Can Joshua Lehmann
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE

import owlkettle, owlkettle/[playground, adw, dataentries]
import std/[strutils, sets, sequtils]

viewable App:
searchDelay: uint = 100
text: string = ""
placeholderText: string = "Search List"
sensitive: bool = true
tooltip: string = ""
sizeRequest: tuple[x, y: int] = (-1, -1)

items: seq[string] = mapIt(0..<100, "Item " & $it)
filteredItems: seq[string] = mapIt(0..<100, "Item " & $it)
selected: int = 0

method view(app: AppState): Widget =
let isInActiveSearch = app.text != ""
result = gui:
Window():
defaultSize = (600, 400)
HeaderBar() {.addTitlebar.}:
insert(app.toAutoFormMenu(ignoreFields = @["filteredItems"])) {.addRight.}

SearchEntry() {.addTitle, expand: true.}:
margin = Margin(top:0, left: 48, bottom:0, right: 48)
text = app.text
searchDelay = app.searchDelay
placeholderText = app.placeholderText
sensitive = app.sensitive
tooltip = app.tooltip
sizeRequest = app.sizeRequest

proc nextMatch() =
if app.selected < app.filteredItems.high:
app.selected += 1

proc previousMatch() =
if app.selected > 0:
app.selected -= 1

proc activate() =
echo "activated search for entry: ": $app.filteredItems[app.selected]

proc changed(searchString: string) =
app.text = searchString
app.selected = 0
app.filteredItems = app.items.filterIt(searchString in it)

proc stopSearch() =
echo "Search Stopped"
app.text = ""
app.filteredItems = app.items

ScrolledWindow:
ListBox:
selected = [app.selected].toHashSet()
selectionMode = SelectionSingle

proc select(rows: Hashset[int]) =
for num in rows:
app.selected = num

for index, item in app.filteredItems:
Box():
Label(text = item, margin = 6) {.hAlign: AlignStart, expand: false.}


adw.brew(gui(App()))
10 changes: 9 additions & 1 deletion owlkettle/gtk.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import ./common

import std/strutils as strutils

const GtkMinor {.intdefine: "gtkminor".}: int = 0 ## Specifies the minimum GTK4 minor version required to run an application. Overwriteable via `-d:gtkminor=X`. Defaults to 0.
const GtkMinor* {.intdefine: "gtkminor".}: int = 0 ## Specifies the minimum GTK4 minor version required to run an application. Overwriteable via `-d:gtkminor=X`. Defaults to 0.

{.passl: strutils.strip(gorge("pkg-config --libs gtk4")).}

Expand Down Expand Up @@ -881,6 +881,14 @@ proc gtk_progress_bar_set_pulse_step*(widget: GtkWidget, fraction: cdouble)
proc gtk_progress_bar_set_show_text*(widget: GtkWidget, show_text: cbool)
proc gtk_progress_bar_set_text*(widget: GtkWidget, text: cstring)

# Gtk.SearchEntry
proc gtk_search_entry_new*(): GtkWidget
proc gtk_search_entry_set_key_capture_widget*(widget: GtkWidget, captureWidget: GtkWidget)
when GtkMinor >= 8:
proc gtk_search_entry_set_search_delay*(widget: GtkWidget, delay: cuint)
when GtkMinor >= 10:
proc gtk_search_entry_set_placeholder_text*(widget: GtkWidget, text: cstring)

# Gtk.Stack
proc gtk_stack_add_named*(stack, child: GtkWidget, name: cstring)
proc gtk_stack_remove*(stack, child: GtkWidget)
Expand Down
64 changes: 62 additions & 2 deletions owlkettle/widgets.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ customPragmas()
when defined(owlkettleDocs) and isMainModule:
echo "# Widgets"

const GtkMinor {.intdefine: "gtkminor".}: int = 0 ## Specifies the minimum GTK4 minor version required to run an application. Overwriteable via `-d:gtkminor=X`. Defaults to 0.

type
Margin* = object
top*, bottom*, left*, right*: int
Expand Down Expand Up @@ -2067,6 +2065,67 @@ renderable ModelButton of BaseWidget:
proc clicked() =
echo "Clicked " & $it

renderable SearchEntry of BaseWidget:
text: string
# child: GtkWidget # This is currently not supported
searchDelay: uint = 100 ## Determines the minimum time after a `searchChanged` event occurred before the next can be emitted. Only available when compiling for gtk 4.8
placeholderText: string = "Search" ## Only available when compiling for gtk 4.10

proc activate() ## Triggered when the user "activated" the search e.g. by hitting "enter" key while SearchEntry is in focus.
proc nextMatch() ## Triggered when the user hits the "next entry" keybinding while the search entry is in focus, which is Ctrl-g by default.
proc previousMatch() ## Triggered when the user hits the "previous entry" keybinding while the search entry is in focus, which is Ctrl-Shift-g by default.
proc changed(searchString: string) ## Triggered when the user types in the SearchEntry.
# proc searchStarted() # Currently not supported
proc stopSearch() ## Triggered when the user "stops" a search, e.g. by hitting the "Esc" key while SearchEntry is in focus.

hooks:
beforeBuild:
state.internalWidget = gtk_search_entry_new()
connectEvents:
proc changedCallback(widget: GtkWidget, data: ptr EventObj[proc(searchString: string)]) =
let searchString = $gtk_editable_get_text(widget)
SearchEntryState(data[].widget).text = searchString
data[].callback(searchString)
data[].redraw()

state.connect(state.activate, "activate", eventCallback)
state.connect(state.nextMatch, "next-match", eventCallback)
state.connect(state.previousMatch, "previous-match", eventCallback)
state.connect(state.changed, "search-changed", changedCallback)
# state.connect(state.searchStarted, "search-changed", eventCallback) # Currently not supported
state.connect(state.stopSearch, "stop-search", eventCallback)
disconnectEvents:
state.internalWidget.disconnect(state.activate)
state.internalWidget.disconnect(state.nextMatch)
state.internalWidget.disconnect(state.previousMatch)
state.internalWidget.disconnect(state.changed)
# state.internalWidget.disconnect(state.searchStarted) # Currently not supported
state.internalWidget.disconnect(state.stopSearch)

# hooks child:
# property:
# gtk_search_entry_set_key_capture_widget(state.internalWidget, state.child.pointer)

hooks text:
property:
gtk_editable_set_text(state.internalWidget, state.text.cstring)
read:
state.text = $gtk_editable_get_text(state.internalWidget)

hooks searchDelay:
property:
when GtkMinor >= 8:
gtk_search_entry_set_search_delay(state.internalWidget, state.searchDelay.cuint)
else:
discard

hooks placeholderText:
property:
when GtkMinor >= 10:
gtk_search_entry_set_placeholder_text(state.internalWidget, state.placeholderText.cstring)
else:
discard

renderable Separator of BaseWidget:
## A separator line.

Expand Down Expand Up @@ -4048,6 +4107,7 @@ export AboutDialog, AboutDialogState
export buildState, updateState, assignAppEvents
export Scale
export Expander
export SearchEntry
export Video
export ProgressBar
export EmojiChooser
Expand Down

0 comments on commit 0ffacf8

Please sign in to comment.