Browse Source
* Adds Window Manager support to the Supervisor, so that Window widgets can be dragged by their title bar, clicked to focus, etc. * Create a ui.Window as normal, but instead of Packing or Placing it into a parent container as before, you call .Supervise() and give it your Supervisor. The window registers itself to be managed and drawn by the Supervisor itself. * Supervisor manages the focused window order using a doubly linked list. When a window takes focus it moves to the top of the list. Widgets in the active window take event priority. * Extended DragDrop API to support holding a widget pointer in the drag operation. * Changed widget event Handle functions to return an error: so that they could return ErrStopPropagation to prevent events going to more widgets once handled (for important events). Some bugs remain around overlapping windows and event propagation.menus
14 changed files with 690 additions and 107 deletions
@ -0,0 +1,94 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"os" |
|||
|
|||
"git.kirsle.net/go/render" |
|||
"git.kirsle.net/go/render/event" |
|||
"git.kirsle.net/go/render/sdl" |
|||
"git.kirsle.net/go/ui" |
|||
) |
|||
|
|||
// Program globals.
|
|||
var ( |
|||
// Size of the MainWindow.
|
|||
Width = 1024 |
|||
Height = 768 |
|||
|
|||
// Cascade offset for creating multiple windows.
|
|||
Cascade = render.NewPoint(10, 10) |
|||
CascadeStep = render.NewPoint(24, 24) |
|||
) |
|||
|
|||
func init() { |
|||
sdl.DefaultFontFilename = "../DejaVuSans.ttf" |
|||
} |
|||
|
|||
func main() { |
|||
mw, err := ui.NewMainWindow("Hello World", Width, Height) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
// Add some windows to play with.
|
|||
addWindow(mw, "First window") |
|||
addWindow(mw, "Second window") |
|||
|
|||
mw.SetBackground(render.White) |
|||
|
|||
mw.OnLoop(func(e *event.State) { |
|||
if e.Escape { |
|||
os.Exit(0) |
|||
} |
|||
}) |
|||
|
|||
mw.MainLoop() |
|||
} |
|||
|
|||
// Add a new child window.
|
|||
func addWindow(mw *ui.MainWindow, title string) { |
|||
win1 := ui.NewWindow(title) |
|||
win1.Configure(ui.Config{ |
|||
Width: 640, |
|||
Height: 480, |
|||
}) |
|||
win1.Compute(mw.Engine) |
|||
win1.Supervise(mw.Supervisor()) |
|||
|
|||
// Attach it to the MainWindow with no placement management, i.e.
|
|||
// instead of Pack() or Place(). Since draggable windows set their own
|
|||
// position, a position manager would only interfere and "snap" the
|
|||
// window back into place as soon as you drop the title bar!
|
|||
// mw.Attach(win1)
|
|||
|
|||
// Default placement via cascade.
|
|||
win1.MoveTo(Cascade) |
|||
Cascade.Add(CascadeStep) |
|||
|
|||
// Add a button to the window.
|
|||
// btn := ui.NewButton("Button1", ui.NewLabel(ui.Label{
|
|||
// Text: "Click me!",
|
|||
// }))
|
|||
// btn.Handle(ui.Click, func(ed ui.EventData) {
|
|||
// fmt.Printf("Window '%s' button clicked!\n", title)
|
|||
// })
|
|||
// mw.Add(btn)
|
|||
// win1.Place(btn, ui.Place{
|
|||
// Top: 10,
|
|||
// Left: 10,
|
|||
// })
|
|||
|
|||
// Add a window duplicator button.
|
|||
btn2 := ui.NewButton(title+":Button2", ui.NewLabel(ui.Label{ |
|||
Text: "New Window", |
|||
})) |
|||
btn2.Handle(ui.Click, func(ed ui.EventData) error { |
|||
addWindow(mw, "New Window") |
|||
return nil |
|||
}) |
|||
mw.Add(btn2) |
|||
win1.Place(btn2, ui.Place{ |
|||
Top: 10, |
|||
Right: 10, |
|||
}) |
|||
} |
@ -0,0 +1,148 @@ |
|||
package ui |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
|
|||
"git.kirsle.net/go/render" |
|||
) |
|||
|
|||
/* |
|||
window_manager.go holds data types and Supervisor methods related to the |
|||
management of ui.Window widgets. |
|||
*/ |
|||
|
|||
// FocusedWindow is a doubly-linked list of recently focused Windows, with
|
|||
// the current and most-recently focused on top. TODO make not exported.
|
|||
type FocusedWindow struct { |
|||
window *Window |
|||
prev *FocusedWindow |
|||
next *FocusedWindow |
|||
} |
|||
|
|||
// String of the FocusedWindow returns the underlying Window's String().
|
|||
func (fw FocusedWindow) String() string { |
|||
return fw.window.String() |
|||
} |
|||
|
|||
// Print the structure of the linked list from top to bottom.
|
|||
func (fw *FocusedWindow) Print() { |
|||
var ( |
|||
node = fw |
|||
i = 0 |
|||
) |
|||
for node != nil { |
|||
fmt.Printf("[%d] window=%s prev=%s next=%s\n", |
|||
i, node.window, node.prev, node.next, |
|||
) |
|||
node = node.next |
|||
i++ |
|||
} |
|||
} |
|||
|
|||
// addWindow installs a Window into the supervisor to be managed. It is called
|
|||
// by ui.Window.Supervise() and the newly added window becomes the focused
|
|||
// one by default at the top of the linked list.
|
|||
func (s *Supervisor) addWindow(win *Window) { |
|||
// Record in the window that it is managed by Supervisor, useful to control
|
|||
// event propagation to non-focused windows.
|
|||
win.managed = true |
|||
|
|||
if s.winFocus == nil { |
|||
// First window added.
|
|||
s.winFocus = &FocusedWindow{ |
|||
window: win, |
|||
} |
|||
s.winTop = s.winFocus |
|||
s.winBottom = s.winFocus |
|||
win.SetFocus(true) |
|||
} else { |
|||
// New window, make it the top one.
|
|||
oldTop := s.winFocus |
|||
s.winFocus = &FocusedWindow{ |
|||
window: win, |
|||
next: oldTop, |
|||
} |
|||
oldTop.prev = s.winFocus |
|||
oldTop.window.SetFocus(false) |
|||
win.SetFocus(true) |
|||
} |
|||
} |
|||
|
|||
// presentWindows draws the windows from bottom to top.
|
|||
func (s *Supervisor) presentWindows(e render.Engine) { |
|||
item := s.winBottom |
|||
for item != nil { |
|||
item.window.Present(e, item.window.Point()) |
|||
item = item.prev |
|||
} |
|||
} |
|||
|
|||
// FocusWindow brings the given window to the top of the supervisor's focus.
|
|||
//
|
|||
// The window must have previously been added to the supervisor's Window Manager
|
|||
// by calling the Supervise() method of the window.
|
|||
func (s *Supervisor) FocusWindow(win *Window) error { |
|||
if s.winFocus == nil { |
|||
return errors.New("no windows managed by supervisor") |
|||
} |
|||
|
|||
// If the top window is already the target, return.
|
|||
if s.winFocus.window == win { |
|||
return nil |
|||
} |
|||
|
|||
// Find the window in the linked list.
|
|||
var ( |
|||
item = s.winFocus // item as we iterate the list
|
|||
oldTop = s.winFocus // original first item in the list
|
|||
target *FocusedWindow // identified target window to raise
|
|||
newBottom *FocusedWindow // if the target was the bottom, this is new bottom
|
|||
i = 0 |
|||
) |
|||
for item != nil { |
|||
if item.window == win { |
|||
// Found it!
|
|||
target = item |
|||
|
|||
// Is it the last window in the list? Record the new bottom node.
|
|||
if item.next == nil && item.prev != nil { |
|||
newBottom = item.prev |
|||
} |
|||
|
|||
// Remove it from its position in the linked list. Join its
|
|||
// previous and next nodes to bridge the gap.
|
|||
if item.next != nil { |
|||
item.next.prev = item.prev |
|||
} |
|||
if item.prev != nil { |
|||
item.prev.next = item.next |
|||
} |
|||
|
|||
break |
|||
} |
|||
item = item.next |
|||
i++ |
|||
} |
|||
|
|||
// Found it?
|
|||
if target != nil { |
|||
// Put the target at the top of the list, pointing to the old top.
|
|||
target.next = oldTop |
|||
target.prev = nil |
|||
oldTop.prev = target |
|||
s.winFocus = target |
|||
|
|||
// Fix the top and bottom pointers.
|
|||
s.winTop = s.winFocus |
|||
if newBottom != nil { |
|||
s.winBottom = newBottom |
|||
} |
|||
|
|||
// Toggle the focus states.
|
|||
oldTop.window.SetFocus(false) |
|||
target.window.SetFocus(true) |
|||
} |
|||
|
|||
return nil |
|||
} |
Loading…
Reference in new issue