diff --git a/README.md b/README.md index 742ca82..f5d6e2a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,23 @@ [![Build Status](https://travis-ci.org/andlabs/ui.png?branch=master)](https://travis-ci.org/andlabs/ui) # Native UI library for Go -### CRITICAL UPDATE 17 March 2014: Due to deadlocks between resizing and setting label text once a second, Labels and Areas do not lock their internal mutex locks after their Window has been created. This seems to not have issues, and the Go race detector isn't saying anything, so IDK... Please help with this if you can. I will have to remove the mutexes from the other classes (later) to make this work properly. +### CRITICAL UPDATE 17 March 2014: Due to deadlocks between resizing and setting label text once a second (see below), all Controls no longer lock their internal mutex locks after their Window has been created. This seems to not have issues, and the Go race detector isn't saying anything, so IDK... Please help spot issues or suggest actual solutions if you can. +The issue: +- the time.Ticker ticks, which causes label.SetText() or whatever to be called; it locks its mutex +- at the same time, a resize event comes in; the resize event runs before the SetText action ever does +- the resize eventually comes to the label, whose resizing functions also try to lock the already-locked mutex +- because the drawing function is now stuck waiting for the mutex to be unlocked, the label.SetText() system-dependent operation never runs, so the mutex is never unlocked +- this fools Go's deadlock detector; it never reports anything +- changing from standard mutexes to R/W mutexes does not work +- making resizes concurrent causes resizes to become too slow to be acceptable (they trail behind the actual user resizing by a significant amount) +If you know a better way I can do things, **please** help... I'm at my wits end here + + + + + + + + ### THIS PACKAGE IS UNDER ACTIVE DEVELOPMENT. It can be used; the API is stable enough at this point, but keep in mind there may still be crashes and API changes, as suggestions are always open. If you can help, please do! Run `./test` to build a test binary `test/test` which runs a (mostly) feature-complete UI test. Run `./d32 ./test` to build a 32-bit version (you will need a cgo-enabled 32-bit go environment, and I have only tested this on Mac OS X). diff --git a/button.go b/button.go index b23e6c7..8815d21 100644 --- a/button.go +++ b/button.go @@ -29,24 +29,22 @@ func NewButton(text string) (b *Button) { // SetText sets the button's text. func (b *Button) SetText(text string) { - b.lock.Lock() - defer b.lock.Unlock() - if b.created { b.sysData.setText(text) return } + b.lock.Lock() + defer b.lock.Unlock() b.initText = text } // Text returns the button's text. func (b *Button) Text() string { - b.lock.Lock() - defer b.lock.Unlock() - if b.created { return b.sysData.text() } + b.lock.Lock() + defer b.lock.Unlock() return b.initText } @@ -64,15 +62,9 @@ func (b *Button) make(window *sysData) error { } func (b *Button) setRect(x int, y int, width int, height int, winheight int) error { - b.lock.Lock() - defer b.lock.Unlock() - return b.sysData.setRect(x, y, width, height, winheight) } func (b *Button) preferredSize() (width int, height int) { - b.lock.Lock() - defer b.lock.Unlock() - return b.sysData.preferredSize() } diff --git a/checkbox.go b/checkbox.go index db488f9..65e25c4 100644 --- a/checkbox.go +++ b/checkbox.go @@ -27,35 +27,32 @@ func NewCheckbox(text string) (c *Checkbox) { // SetText sets the checkbox's text. func (c *Checkbox) SetText(text string) { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { c.sysData.setText(text) return } + c.lock.Lock() + defer c.lock.Unlock() c.initText = text } // Text returns the checkbox's text. func (c *Checkbox) Text() string { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { return c.sysData.text() } + c.lock.Lock() + defer c.lock.Unlock() return c.initText } // Checked() returns whether or not the checkbox has been checked. func (c *Checkbox) Checked() bool { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { return c.sysData.isChecked() } + c.lock.Lock() + defer c.lock.Unlock() return false } @@ -72,15 +69,9 @@ func (c *Checkbox) make(window *sysData) error { } func (c *Checkbox) setRect(x int, y int, width int, height int, winheight int) error { - c.lock.Lock() - defer c.lock.Unlock() - return c.sysData.setRect(x, y, width, height, winheight) } func (c *Checkbox) preferredSize() (width int, height int) { - c.lock.Lock() - defer c.lock.Unlock() - return c.sysData.preferredSize() } diff --git a/combobox.go b/combobox.go index f0afd19..41960c4 100644 --- a/combobox.go +++ b/combobox.go @@ -39,24 +39,20 @@ func NewEditableCombobox(items ...string) *Combobox { // Append adds items to the end of the Combobox's list. // Append will panic if something goes wrong on platforms that do not abort themselves. func (c *Combobox) Append(what ...string) { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { for _, s := range what { c.sysData.append(s) } return } + c.lock.Lock() + defer c.lock.Unlock() c.initItems = append(c.initItems, what...) } // InsertBefore inserts a new item in the Combobox before the item at the given position. It panics if the given index is out of bounds. // InsertBefore will also panic if something goes wrong on platforms that do not abort themselves. func (c *Combobox) InsertBefore(what string, before int) { - c.lock.Lock() - defer c.lock.Unlock() - var m []string if c.created { @@ -66,6 +62,8 @@ func (c *Combobox) InsertBefore(what string, before int) { c.sysData.insertBefore(what, before) return } + c.lock.Lock() + defer c.lock.Unlock() if before < 0 || before >= len(c.initItems) { goto badrange } @@ -80,9 +78,6 @@ badrange: // Delete removes the given item from the Combobox. It panics if the given index is out of bounds. func (c *Combobox) Delete(index int) { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { if index < 0 || index >= c.sysData.len() { goto badrange @@ -90,6 +85,8 @@ func (c *Combobox) Delete(index int) { c.sysData.delete(index) return } + c.lock.Lock() + defer c.lock.Unlock() if index < 0 || index >= len(c.initItems) { goto badrange } @@ -101,23 +98,21 @@ badrange: // Selection returns the current selection. func (c *Combobox) Selection() string { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { return c.sysData.text() } + c.lock.Lock() + defer c.lock.Unlock() return "" } // SelectedIndex returns the index of the current selection in the Combobox. It returns -1 either if no selection was made or if text was manually entered in an editable Combobox. func (c *Combobox) SelectedIndex() int { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { return c.sysData.selectedIndex() } + c.lock.Lock() + defer c.lock.Unlock() return -1 } @@ -125,12 +120,11 @@ func (c *Combobox) SelectedIndex() int { // // On platforms for which this function may return an error, it panics if one is returned. func (c *Combobox) Len() int { - c.lock.Lock() - defer c.lock.Unlock() - if c.created { return c.sysData.len() } + c.lock.Lock() + defer c.lock.Unlock() return len(c.initItems) } @@ -150,15 +144,9 @@ func (c *Combobox) make(window *sysData) (err error) { } func (c *Combobox) setRect(x int, y int, width int, height int, winheight int) error { - c.lock.Lock() - defer c.lock.Unlock() - return c.sysData.setRect(x, y, width, height, winheight) } func (c *Combobox) preferredSize() (width int, height int) { - c.lock.Lock() - defer c.lock.Unlock() - return c.sysData.preferredSize() } diff --git a/grid.go b/grid.go index 007a04a..309c5db 100644 --- a/grid.go +++ b/grid.go @@ -121,9 +121,6 @@ func (g *Grid) make(window *sysData) error { } func (g *Grid) setRect(x int, y int, width int, height int, winheight int) error { - g.lock.Lock() - defer g.lock.Unlock() - max := func(a int, b int) int { if a > b { return a @@ -188,9 +185,6 @@ func (g *Grid) setRect(x int, y int, width int, height int, winheight int) error // filling and stretchy are ignored for preferred size calculation func (g *Grid) preferredSize() (width int, height int) { - g.lock.Lock() - defer g.lock.Unlock() - max := func(a int, b int) int { if a > b { return a diff --git a/lineedit.go b/lineedit.go index 2321227..2d89a5c 100644 --- a/lineedit.go +++ b/lineedit.go @@ -35,24 +35,22 @@ func NewPasswordEdit() *LineEdit { // SetText sets the LineEdit's text. func (l *LineEdit) SetText(text string) { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { l.sysData.setText(text) return } + l.lock.Lock() + defer l.lock.Unlock() l.initText = text } // Text returns the LineEdit's text. func (l *LineEdit) Text() string { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { return l.sysData.text() } + l.lock.Lock() + defer l.lock.Unlock() return l.initText } @@ -70,15 +68,9 @@ func (l *LineEdit) make(window *sysData) error { } func (l *LineEdit) setRect(x int, y int, width int, height int, winheight int) error { - l.lock.Lock() - defer l.lock.Unlock() - return l.sysData.setRect(x, y, width, height, winheight) } func (l *LineEdit) preferredSize() (width int, height int) { - l.lock.Lock() - defer l.lock.Unlock() - return l.sysData.preferredSize() } diff --git a/listbox.go b/listbox.go index 3fe6649..36064f2 100644 --- a/listbox.go +++ b/listbox.go @@ -40,24 +40,20 @@ func NewMultiSelListbox(items ...string) *Listbox { // Append adds items to the end of the Listbox's list. // Append will panic if something goes wrong on platforms that do not abort themselves. func (l *Listbox) Append(what ...string) { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { for _, s := range what { l.sysData.append(s) } return } + l.lock.Lock() + defer l.lock.Unlock() l.initItems = append(l.initItems, what...) } // InsertBefore inserts a new item in the Listbox before the item at the given position. It panics if the given index is out of bounds. // InsertBefore will also panic if something goes wrong on platforms that do not abort themselves. func (l *Listbox) InsertBefore(what string, before int) { - l.lock.Lock() - defer l.lock.Unlock() - var m []string if l.created { @@ -67,6 +63,8 @@ func (l *Listbox) InsertBefore(what string, before int) { l.sysData.insertBefore(what, before) return } + l.lock.Lock() + defer l.lock.Unlock() if before < 0 || before >= len(l.initItems) { goto badrange } @@ -81,9 +79,6 @@ badrange: // Delete removes the given item from the Listbox. It panics if the given index is out of bounds. func (l *Listbox) Delete(index int) { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { if index < 0 || index >= l.sysData.len() { goto badrange @@ -91,6 +86,8 @@ func (l *Listbox) Delete(index int) { l.sysData.delete(index) return } + l.lock.Lock() + defer l.lock.Unlock() if index < 0 || index >= len(l.initItems) { goto badrange } @@ -102,23 +99,21 @@ badrange: // Selection returns a list of strings currently selected in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox. func (l *Listbox) Selection() []string { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { return l.sysData.selectedTexts() } + l.lock.Lock() + defer l.lock.Unlock() return nil } // SelectedIndices returns a list of the currently selected indexes in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox. func (l *Listbox) SelectedIndices() []int { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { return l.sysData.selectedIndices() } + l.lock.Lock() + defer l.lock.Unlock() return nil } @@ -126,12 +121,11 @@ func (l *Listbox) SelectedIndices() []int { // // On platforms for which this function may return an error, it panics if one is returned. func (l *Listbox) Len() int { - l.lock.Lock() - defer l.lock.Unlock() - if l.created { return l.sysData.len() } + l.lock.Lock() + defer l.lock.Unlock() return len(l.initItems) } @@ -151,15 +145,9 @@ func (l *Listbox) make(window *sysData) (err error) { } func (l *Listbox) setRect(x int, y int, width int, height int, winheight int) error { - l.lock.Lock() - defer l.lock.Unlock() - return l.sysData.setRect(x, y, width, height, winheight) } func (l *Listbox) preferredSize() (width int, height int) { - l.lock.Lock() - defer l.lock.Unlock() - return l.sysData.preferredSize() } diff --git a/progressbar.go b/progressbar.go index da89d1a..cd4dc98 100644 --- a/progressbar.go +++ b/progressbar.go @@ -30,9 +30,6 @@ func NewProgressBar() *ProgressBar { // Otherwise, SetProgress panics. // TODO what happens if you repeatedly call SetProgress(-1)? func (p *ProgressBar) SetProgress(percent int) { - p.lock.Lock() - defer p.lock.Unlock() - if percent < -1 || percent > 100 { panic("percent value out of range") } @@ -40,6 +37,8 @@ func (p *ProgressBar) SetProgress(percent int) { p.sysData.setProgress(percent) return } + p.lock.Lock() + defer p.lock.Unlock() p.initProg = percent } @@ -57,15 +56,9 @@ func (p *ProgressBar) make(window *sysData) error { } func (p *ProgressBar) setRect(x int, y int, width int, height int, winheight int) error { - p.lock.Lock() - defer p.lock.Unlock() - return p.sysData.setRect(x, y, width, height, winheight) } func (p *ProgressBar) preferredSize() (width int, height int) { - p.lock.Lock() - defer p.lock.Unlock() - return p.sysData.preferredSize() } diff --git a/stack.go b/stack.go index c6335ef..b37b935 100644 --- a/stack.go +++ b/stack.go @@ -78,9 +78,6 @@ func (s *Stack) make(window *sysData) error { } func (s *Stack) setRect(x int, y int, width int, height int, winheight int) error { - s.lock.Lock() - defer s.lock.Unlock() - var stretchywid, stretchyht int if len(s.controls) == 0 { // do nothing if there's nothing to do @@ -138,9 +135,6 @@ func (s *Stack) setRect(x int, y int, width int, height int, winheight int) erro // The preferred size of a Stack is the sum of the preferred sizes of non-stretchy controls + (the number of stretchy controls * the largest preferred size among all stretchy controls). func (s *Stack) preferredSize() (width int, height int) { - s.lock.Lock() - defer s.lock.Unlock() - max := func(a int, b int) int { if a > b { return a