Skip to content

Commit 5cbb735

Browse files
Example of TableView (#217)
- Shows how to create a TableView - Dynamically updates data in the table
1 parent 53ff09c commit 5cbb735

File tree

3 files changed

+425
-1
lines changed

3 files changed

+425
-1
lines changed

macos/_examples/tableview/main.go

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/progrium/macdriver/dispatch"
7+
"github.com/progrium/macdriver/macos/appkit"
8+
"github.com/progrium/macdriver/macos/foundation"
9+
"github.com/progrium/macdriver/objc"
10+
"runtime"
11+
"sync"
12+
"time"
13+
)
14+
15+
const (
16+
RowHeight = 20
17+
)
18+
19+
type App struct {
20+
app appkit.Application
21+
windowClosed bool
22+
doStop func()
23+
}
24+
25+
func (a *App) Stop() {
26+
a.doStop()
27+
}
28+
29+
func newApp(app appkit.Application) *App {
30+
return &App{
31+
app: app,
32+
}
33+
}
34+
35+
func main() {
36+
runtime.LockOSThread()
37+
38+
app := appkit.Application_SharedApplication()
39+
tvApp := newApp(app)
40+
delegate := &appkit.ApplicationDelegate{}
41+
delegate.SetApplicationDidFinishLaunching(func(foundation.Notification) {
42+
tvApp.mainWindow()
43+
})
44+
delegate.SetApplicationWillFinishLaunching(func(foundation.Notification) {
45+
tvApp.setMainMenu()
46+
})
47+
delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool {
48+
return true
49+
})
50+
delegate.SetApplicationWillTerminate(func(foundation.Notification) {
51+
tvApp.Stop()
52+
})
53+
54+
app.SetDelegate(delegate)
55+
app.Run()
56+
}
57+
58+
type TableViewDataSource struct {
59+
values chan [][2]objc.Object
60+
data [][2]objc.Object
61+
delegate *TableViewDataSourceDelegate
62+
wg *sync.WaitGroup
63+
maxRows int
64+
overflowValue int
65+
}
66+
67+
func (t *TableViewDataSource) Wait() {
68+
t.wg.Wait()
69+
}
70+
71+
func (t *TableViewDataSource) Start(ctx context.Context, tableView appkit.TableView) {
72+
t.wg.Add(1)
73+
go valueGenerator(ctx, t.wg, t.values, tableView, t.maxRows, t.overflowValue)
74+
}
75+
76+
func valueGenerator(ctx context.Context, wg *sync.WaitGroup, values chan<- [][2]objc.Object, tableView appkit.TableView, maxRows int, overflowValue int) {
77+
defer wg.Done()
78+
currentValues := make([][2]int, maxRows)
79+
sendToUI := func() {
80+
snapshot := make([][2]objc.Object, len(currentValues))
81+
for i, v := range currentValues {
82+
snapshot[i] = [2]objc.Object{foundation.String_StringWithString(fmt.Sprintf("%d", v[0])).Object, foundation.String_StringWithString(fmt.Sprintf("%d", v[1])).Object}
83+
}
84+
select {
85+
case <-ctx.Done():
86+
return
87+
case values <- snapshot:
88+
dispatch.MainQueue().DispatchAsync(func() {
89+
tableView.SetNeedsDisplay(true)
90+
})
91+
}
92+
}
93+
counter := 0
94+
row := 0
95+
for {
96+
row = counter / 2
97+
if row >= maxRows {
98+
row = 0
99+
break
100+
}
101+
currentValues[row][counter%2] = counter % overflowValue
102+
counter++
103+
}
104+
sendToUI()
105+
106+
for {
107+
select {
108+
case <-ctx.Done():
109+
return
110+
default:
111+
time.Sleep(10 * time.Millisecond)
112+
row = (counter / 2) % maxRows
113+
currentValues[row][counter%2] = counter % overflowValue
114+
counter++
115+
sendToUI()
116+
}
117+
}
118+
119+
}
120+
121+
func (t *TableViewDataSource) getLatest(ctx context.Context) {
122+
switch t.data {
123+
case nil:
124+
select {
125+
case t.data = <-t.values:
126+
case <-ctx.Done():
127+
t.data = nil
128+
return
129+
}
130+
default:
131+
select {
132+
case t.data = <-t.values:
133+
case <-ctx.Done():
134+
t.data = nil
135+
return
136+
default:
137+
}
138+
}
139+
}
140+
141+
func NewTableViewDataSource(ctx context.Context, maxRows int, overflowValue int) *TableViewDataSource {
142+
model := &TableViewDataSource{
143+
maxRows: maxRows,
144+
overflowValue: overflowValue,
145+
values: make(chan [][2]objc.Object, 1),
146+
data: nil,
147+
delegate: &TableViewDataSourceDelegate{},
148+
wg: &sync.WaitGroup{},
149+
}
150+
model.delegate.SetTableViewObjectValueForTableColumnRow(func(tableView appkit.TableView, tableColumn appkit.TableColumn, row int) objc.Object {
151+
model.getLatest(ctx)
152+
if model.data == nil {
153+
return objc.ObjectFrom(nil)
154+
}
155+
switch tableColumn.Identifier() {
156+
case "Column1":
157+
return model.data[row][0]
158+
case "Column2":
159+
return model.data[row][1]
160+
}
161+
panic("unknown column")
162+
})
163+
model.delegate.SetNumberOfRowsInTableView(func(tableView appkit.TableView) int {
164+
model.getLatest(ctx)
165+
return len(model.data)
166+
})
167+
return model
168+
}
169+
170+
func (a *App) mainWindow() {
171+
w := appkit.NewWindowWithSize(300, 400)
172+
objc.Retain(&w)
173+
a.windowClosed = false
174+
175+
wd := &appkit.WindowDelegate{}
176+
wd.SetWindowWillClose(func(notification foundation.Notification) {
177+
a.windowClosed = true
178+
})
179+
w.SetDelegate(wd)
180+
181+
ctx := context.Background()
182+
ctx, cancel := context.WithCancel(ctx)
183+
dataSource := NewTableViewDataSource(ctx, 40, 13)
184+
a.doStop = func() {
185+
cancel()
186+
dataSource.Wait()
187+
if !a.windowClosed {
188+
w.Close()
189+
}
190+
}
191+
192+
w.SetTitle("Test table view")
193+
194+
tableView := appkit.NewTableView()
195+
go dataSource.Start(ctx, tableView)
196+
tableView.SetRowHeight(RowHeight)
197+
tableView.SetTranslatesAutoresizingMaskIntoConstraints(false)
198+
tableView.SetHeaderView(appkit.NewTableHeaderViewWithFrame(rectOf(0, 0, 0, RowHeight)))
199+
tableView.SetGridStyleMask(appkit.TableViewSolidVerticalGridLineMask | appkit.TableViewSolidHorizontalGridLineMask)
200+
tableView.SetStyle(appkit.TableViewStylePlain)
201+
tableView.SetRowSizeStyle(appkit.TableViewRowSizeStyleDefault)
202+
tableView.SetColumnAutoresizingStyle(appkit.TableViewUniformColumnAutoresizingStyle)
203+
tableView.SetUsesAlternatingRowBackgroundColors(true)
204+
tableView.SetStyle(appkit.TableViewStyleFullWidth)
205+
tableView.SetTranslatesAutoresizingMaskIntoConstraints(false)
206+
tableColumn1 := appkit.NewTableColumn().InitWithIdentifier("Column1")
207+
tableColumn1.SetTitle("Test 1")
208+
tableColumn1.SetWidth(100)
209+
tableView.AddTableColumn(tableColumn1)
210+
tableColumn2 := appkit.NewTableColumn().InitWithIdentifier("Column2")
211+
tableColumn2.SetTitle("Test 2")
212+
tableColumn2.SetWidth(100)
213+
tableView.AddTableColumn(tableColumn2)
214+
tableView.SetDataSource(dataSource.delegate)
215+
tableView.SetAllowsColumnSelection(true)
216+
tableView.SetAutoresizingMask(appkit.ViewWidthSizable | appkit.ViewHeightSizable)
217+
218+
tsv := appkit.NewScrollView()
219+
tsv.SetFrameSize(foundation.Size{Width: w.ContentView().Frame().Size.Width, Height: w.ContentView().Frame().Size.Height})
220+
tsv.SetFrameOrigin(foundation.Point{X: w.ContentView().Frame().Origin.X, Y: w.ContentView().Frame().Origin.Y})
221+
tsv.SetTranslatesAutoresizingMaskIntoConstraints(false)
222+
tsv.SetDocumentView(tableView)
223+
tsv.SetHasHorizontalScroller(true)
224+
tsv.SetHasVerticalScroller(true)
225+
tsv.SetAutohidesScrollers(true)
226+
tsv.SetTranslatesAutoresizingMaskIntoConstraints(true)
227+
tsv.SetAutoresizingMask(appkit.ViewWidthSizable | appkit.ViewHeightSizable)
228+
w.ContentView().AddSubview(tsv)
229+
w.ContentView().LeadingAnchor().ConstraintEqualToAnchorConstant(tsv.LeadingAnchor(), -10).SetActive(true)
230+
w.ContentView().TopAnchor().ConstraintEqualToAnchorConstant(tsv.TopAnchor(), -10).SetActive(true)
231+
w.ContentView().TrailingAnchor().ConstraintEqualToAnchorConstant(tsv.TrailingAnchor(), 10).SetActive(true)
232+
w.ContentView().BottomAnchor().ConstraintEqualToAnchorConstant(tsv.BottomAnchor(), 10).SetActive(true)
233+
234+
w.Center()
235+
w.MakeKeyAndOrderFront(nil)
236+
237+
a.app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular)
238+
a.app.ActivateIgnoringOtherApps(true)
239+
}
240+
241+
func (a *App) setMainMenu() {
242+
menuBar := appkit.NewMenuWithTitle("Table View")
243+
a.app.SetMainMenu(menuBar)
244+
245+
appMenuItem := appkit.NewMenuItemWithSelector("Table View", "", objc.Selector{})
246+
appMenu := appkit.NewMenuWithTitle("Table View")
247+
appMenu.AddItem(appkit.NewMenuItemWithAction("Quit", "q", func(sender objc.Object) { a.app.Terminate(nil) }))
248+
appMenuItem.SetSubmenu(appMenu)
249+
menuBar.AddItem(appMenuItem)
250+
}
251+
252+
func rectOf(x, y, width, height float64) foundation.Rect {
253+
return foundation.Rect{Origin: foundation.Point{X: x, Y: y}, Size: foundation.Size{Width: width, Height: height}}
254+
}

0 commit comments

Comments
 (0)