Dumped the mutex locks from the other controls and elaborated/asked for help in the README.
This commit is contained in:
parent
bdb26046b6
commit
0e8680c04f
19
README.md
19
README.md
|
@ -1,6 +1,23 @@
|
|||
[](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).
|
||||
|
||||
|
|
16
button.go
16
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()
|
||||
}
|
||||
|
|
21
checkbox.go
21
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()
|
||||
}
|
||||
|
|
36
combobox.go
36
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()
|
||||
}
|
||||
|
|
6
grid.go
6
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
|
||||
|
|
16
lineedit.go
16
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()
|
||||
}
|
||||
|
|
36
listbox.go
36
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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
6
stack.go
6
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
|
||||
|
|
Loading…
Reference in New Issue