User interface toolkit for Go with support for SDL2 and HTML Canvas render targets.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 

531 行
14 KiB

  1. package ui
  2. import (
  3. "errors"
  4. "sync"
  5. "git.kirsle.net/go/render"
  6. "git.kirsle.net/go/render/event"
  7. )
  8. // Event is a named event that the supervisor will send.
  9. type Event int
  10. // Events.
  11. const (
  12. NullEvent Event = iota
  13. MouseOver
  14. MouseOut
  15. MouseDown
  16. MouseUp
  17. Click
  18. KeyDown
  19. KeyUp
  20. KeyPress
  21. // Drag/drop event handlers.
  22. DragStop // if a widget is being dragged and the drag is done
  23. DragMove // mouse movements sent to a widget being dragged.
  24. Drop // a "drop site" widget under the cursor when a drag is done
  25. // Window Manager events.
  26. CloseWindow
  27. MaximizeWindow
  28. MinimizeWindow
  29. CloseModal
  30. // Lifecycle event handlers.
  31. Compute // fired whenever the widget runs Compute
  32. Present // fired whenever the widget runs Present
  33. // Form field events.
  34. Change
  35. )
  36. // EventData carries common data to event handlers.
  37. type EventData struct {
  38. // Point is usually the cursor position on click and mouse events.
  39. Point render.Point
  40. // Engine is the render engine on Compute and Present events.
  41. Engine render.Engine
  42. // Supervisor is the reference to the supervisor who sent the event.
  43. Supervisor *Supervisor
  44. }
  45. // Supervisor keeps track of widgets of interest to notify them about
  46. // interaction events such as mouse hovers and clicks in their general
  47. // vicinity.
  48. type Supervisor struct {
  49. lock sync.RWMutex
  50. serial int // ID number of each widget added in order
  51. widgets map[int]WidgetSlot // map of widget ID to WidgetSlot
  52. hovering map[int]interface{} // map of widgets under the cursor
  53. clicked map[int]bool // map of widgets being clicked
  54. dd *DragDrop
  55. // Stack of modal widgets that have event priority.
  56. modals []Widget
  57. // List of window focus history for Window Manager.
  58. winFocus *FocusedWindow
  59. winTop *FocusedWindow // pointer to top-most window
  60. winBottom *FocusedWindow // pointer to bottom-most window
  61. }
  62. // WidgetSlot holds a widget with a unique ID number in a sorted list.
  63. type WidgetSlot struct {
  64. id int
  65. widget Widget
  66. }
  67. // NewSupervisor creates a supervisor.
  68. func NewSupervisor() *Supervisor {
  69. return &Supervisor{
  70. widgets: map[int]WidgetSlot{},
  71. hovering: map[int]interface{}{},
  72. clicked: map[int]bool{},
  73. modals: []Widget{},
  74. dd: NewDragDrop(),
  75. }
  76. }
  77. // DragStart sets the drag state without a widget.
  78. //
  79. // An example where you'd use this is if you want a widget to respond to a
  80. // Drop event (mouse released over a drop-site widget) but the 'thing' being
  81. // dragged is not a ui.Widget, i.e., for custom app specific logic.
  82. func (s *Supervisor) DragStart() {
  83. s.dd.Start()
  84. }
  85. // DragStartWidget sets the drag state to true with a target widget attached.
  86. //
  87. // The widget being dragged is given DragMove events while the drag is
  88. // underway. When the mouse button is released, the widget is given a
  89. // DragStop event and the widget below the cursor is given a Drop event.
  90. func (s *Supervisor) DragStartWidget(w Widget) {
  91. s.dd.SetWidget(w)
  92. s.dd.Start()
  93. }
  94. // DragStop stops the drag state.
  95. func (s *Supervisor) DragStop() {
  96. s.dd.Stop()
  97. }
  98. // IsDragging returns whether the drag state is enabled.
  99. func (s *Supervisor) IsDragging() bool {
  100. return s.dd.IsDragging()
  101. }
  102. // Error messages that may be returned by Supervisor.Loop()
  103. var (
  104. // The caller should STOP forwarding any mouse or keyboard events to any
  105. // other handles for the remainder of this tick.
  106. ErrStopPropagation = errors.New("stop all event propagation")
  107. ErrNoEventHandler = errors.New("no event handler")
  108. )
  109. // Loop to check events and pass them to managed widgets.
  110. //
  111. // Useful errors returned by this may be:
  112. // - ErrStopPropagation
  113. func (s *Supervisor) Loop(ev *event.State) error {
  114. var (
  115. XY = render.Point{
  116. X: ev.CursorX,
  117. Y: ev.CursorY,
  118. }
  119. )
  120. // See if we are hovering over any widgets.
  121. hovering, outside := s.Hovering(XY)
  122. // If we are dragging something around, do not trigger any mouse events
  123. // to other widgets but DO notify any widget we dropped on top of!
  124. if s.dd.IsDragging() {
  125. if !ev.Button1 && !ev.Button3 {
  126. // The mouse has been released. TODO: make mouse button important?
  127. for _, child := range hovering {
  128. child.widget.Event(Drop, EventData{
  129. Point: XY,
  130. })
  131. }
  132. s.DragStop()
  133. } else {
  134. // If we have a target widget being dragged, send it mouse events.
  135. if target := s.dd.Widget(); target != nil {
  136. target.Event(DragMove, EventData{
  137. Point: XY,
  138. })
  139. }
  140. }
  141. return ErrStopPropagation
  142. }
  143. // Check if the top focused window has been closed and auto-focus the next.
  144. if s.winFocus != nil && s.winFocus.window.Hidden() {
  145. next := s.winFocus.next
  146. for next != nil {
  147. if !next.window.Hidden() {
  148. s.FocusWindow(next.window)
  149. break
  150. }
  151. next = next.next
  152. }
  153. }
  154. // Run events in managed windows first, from top to bottom.
  155. // Widgets in unmanaged windows will be handled next.
  156. // err := s.runWindowEvents(XY, ev, hovering, outside)
  157. // Only run if there is no active modal (modals have top priority)
  158. if len(s.modals) == 0 {
  159. handled, err := s.runWidgetEvents(XY, ev, hovering, outside, true)
  160. if err == ErrStopPropagation || handled {
  161. // A widget in the active window has accepted an event. Do not pass
  162. // the event also to lower widgets.
  163. return ErrStopPropagation
  164. }
  165. }
  166. // Run events for the other widgets not in a managed window.
  167. // (Modal event priority is handled in runWidgetEvents)
  168. s.runWidgetEvents(XY, ev, hovering, outside, false)
  169. return nil
  170. }
  171. // Hovering returns all of the widgets managed by Supervisor that are under
  172. // the mouse cursor. Returns the set of widgets below the cursor and the set
  173. // of widgets not below the cursor.
  174. func (s *Supervisor) Hovering(cursor render.Point) (hovering, outside []WidgetSlot) {
  175. var XY = cursor // for shorthand
  176. hovering = []WidgetSlot{}
  177. outside = []WidgetSlot{}
  178. // Check all the widgets under our care.
  179. for child := range s.Widgets() {
  180. var (
  181. w = child.widget
  182. P = AbsolutePosition(w)
  183. S = w.Size()
  184. P2 = render.Point{
  185. X: P.X + S.W,
  186. Y: P.Y + S.H,
  187. }
  188. )
  189. if XY.X >= P.X && XY.X < P2.X && XY.Y >= P.Y && XY.Y < P2.Y {
  190. // Cursor intersects the widget.
  191. hovering = append(hovering, child)
  192. } else {
  193. outside = append(outside, child)
  194. }
  195. }
  196. return hovering, outside
  197. }
  198. // runWindowEvents is a subroutine of Supervisor.Loop().
  199. //
  200. // After determining the widgets below the cursor (hovering) and outside the
  201. // cursor, transmit mouse events to the widgets.
  202. //
  203. // This function has two use cases:
  204. // - In runWindowEvents where we run events for the top-most focused window of
  205. // the window manager.
  206. // - In Supervisor.Loop() for the widgets that are NOT owned by a managed
  207. // window, so that these widgets always get events.
  208. //
  209. // Parameters:
  210. // XY (Point): mouse cursor position as calculated in Loop()
  211. // ev, hovering, outside: values from Loop(), self explanatory.
  212. // behavior: indicates how this method is being used.
  213. //
  214. // behavior options:
  215. // 0: widgets NOT part of a managed window. On this pass, if a widget IS
  216. // a part of a window, it gets no events triggered.
  217. // 1: widgets are part of the active focused window.
  218. func (s *Supervisor) runWidgetEvents(XY render.Point, ev *event.State,
  219. hovering, outside []WidgetSlot, toFocusedWindow bool) (bool, error) {
  220. // Do we run any events?
  221. var (
  222. stopPropagation bool
  223. ranEvents bool
  224. )
  225. // Do we have active modals? Modal widgets have top event priority given
  226. // only to the top-most modal.
  227. var modal Widget
  228. if len(s.modals) > 0 {
  229. modal = s.modals[len(s.modals)-1]
  230. }
  231. // If we're running this method in "Phase 2" (to widgets NOT in the focused
  232. // window), only send mouse events to widgets if the cursor is NOT inside
  233. // the bounding box of the active focused window. Prevents clicking "thru"
  234. // the window and activating widgets/other windows behind it.
  235. var cursorInsideFocusedWindow bool
  236. if !toFocusedWindow && s.winFocus != nil && !s.winFocus.window.Hidden() {
  237. // Get the bounding box of the focused window.
  238. if XY.Inside(AbsoluteRect(s.winFocus.window)) {
  239. cursorInsideFocusedWindow = true
  240. }
  241. }
  242. // Handler for an Event response errors.
  243. handle := func(err error) {
  244. // Did any event handler run?
  245. if err != ErrNoEventHandler {
  246. ranEvents = true
  247. }
  248. // Are we stopping propagation?
  249. if err == ErrStopPropagation {
  250. stopPropagation = true
  251. }
  252. }
  253. for _, child := range hovering {
  254. if stopPropagation {
  255. break
  256. }
  257. // If the cursor is inside the box of the focused window, don't trigger
  258. // active (hovering) mouse events. MouseOut type events, below, can still
  259. // trigger.
  260. // Does not apply when a modal widget is active.
  261. if cursorInsideFocusedWindow && modal == nil {
  262. break
  263. }
  264. var (
  265. id = child.id
  266. w = child.widget
  267. )
  268. if w.Hidden() {
  269. // TODO: somehow the Supervisor wasn't triggering hidden widgets
  270. // anyway, but I don't know why. Adding this check for safety.
  271. continue
  272. }
  273. // If we have a modal active, validate this widget is a child of
  274. // the modal widget.
  275. if modal != nil {
  276. if !HasParent(w, modal) {
  277. continue
  278. }
  279. }
  280. // Check if the widget is part of a Window managed by Supervisor.
  281. isManaged, isFocused := widgetInFocusedWindow(w)
  282. // Are we sending events to it?
  283. if toFocusedWindow {
  284. // Only sending events to widgets owned by the focused window.
  285. if !(isManaged && isFocused) {
  286. continue
  287. }
  288. } else {
  289. // Sending only to widgets NOT managed by a window. This can include
  290. // Window widgets themselves, so lower unfocused windows may be
  291. // brought to foreground.
  292. window, isWindow := w.(*Window)
  293. if isManaged && !isWindow {
  294. continue
  295. }
  296. // It is a window, but can only be the non-focused window.
  297. if isWindow && window.focused {
  298. continue
  299. }
  300. }
  301. // Cursor has intersected the widget.
  302. if _, ok := s.hovering[id]; !ok {
  303. handle(w.Event(MouseOver, EventData{
  304. Point: XY,
  305. }))
  306. s.hovering[id] = nil
  307. }
  308. isClicked, _ := s.clicked[id]
  309. if ev.Button1 {
  310. if !isClicked {
  311. err := w.Event(MouseDown, EventData{
  312. Point: XY,
  313. })
  314. handle(err)
  315. s.clicked[id] = true
  316. }
  317. } else if isClicked {
  318. handle(w.Event(MouseUp, EventData{
  319. Point: XY,
  320. }))
  321. handle(w.Event(Click, EventData{
  322. Point: XY,
  323. }))
  324. delete(s.clicked, id)
  325. }
  326. }
  327. for _, child := range outside {
  328. var (
  329. id = child.id
  330. w = child.widget
  331. )
  332. // If we have a modal active, validate this widget is a child of
  333. // the modal widget.
  334. if modal != nil {
  335. if !HasParent(w, modal) {
  336. continue
  337. }
  338. }
  339. // Cursor is not intersecting the widget.
  340. if _, ok := s.hovering[id]; ok {
  341. handle(w.Event(MouseOut, EventData{
  342. Point: XY,
  343. }))
  344. delete(s.hovering, id)
  345. }
  346. if _, ok := s.clicked[id]; ok {
  347. handle(w.Event(MouseUp, EventData{
  348. Point: XY,
  349. }))
  350. delete(s.clicked, id)
  351. }
  352. }
  353. // If a modal is active and a click was registered outside the modal's
  354. // bounding box, send the CloseModal event.
  355. if modal != nil && !XY.Inside(AbsoluteRect(modal)) {
  356. if ev.Button1 {
  357. modal.Event(CloseModal, EventData{
  358. Supervisor: s,
  359. })
  360. }
  361. }
  362. // If there was a modal, return stopPropagation (so callers that manage
  363. // events externally of go/ui can see that a modal intercepted events)
  364. if modal != nil {
  365. return ranEvents, ErrStopPropagation
  366. }
  367. // If a stopPropagation was called, return it up the stack.
  368. if stopPropagation {
  369. return ranEvents, ErrStopPropagation
  370. }
  371. // If ANY event handler was called, return nil to signal
  372. return ranEvents, nil
  373. }
  374. // Widgets returns a channel of widgets managed by the supervisor in the order
  375. // they were added.
  376. func (s *Supervisor) Widgets() <-chan WidgetSlot {
  377. pipe := make(chan WidgetSlot)
  378. go func() {
  379. for i := 0; i < s.serial; i++ {
  380. if w, ok := s.widgets[i]; ok {
  381. pipe <- w
  382. }
  383. }
  384. close(pipe)
  385. }()
  386. return pipe
  387. }
  388. // Present all widgets managed by the supervisor.
  389. //
  390. // NOTE: only the Window Manager feature uses this method, and this method
  391. // will render the windows from bottom to top with the focused window on top.
  392. // For other widgets, they should be added to a parent Frame that will call
  393. // Present on them each time the parent Presents, or otherwise you need to
  394. // manage the presentation of widgets outside the Supervisor.
  395. func (s *Supervisor) Present(e render.Engine) {
  396. s.lock.RLock()
  397. defer s.lock.RUnlock()
  398. // Render the window manager windows from bottom to top.
  399. s.presentWindows(e)
  400. // Render the modals from bottom to top.
  401. if len(s.modals) > 0 {
  402. for _, modal := range s.modals {
  403. modal.Present(e, modal.Point())
  404. }
  405. }
  406. }
  407. // Add a widget to be supervised. Has no effect if the widget is already
  408. // under the supervisor's care.
  409. func (s *Supervisor) Add(w Widget) {
  410. s.lock.Lock()
  411. // Check it's not already there.
  412. for _, child := range s.widgets {
  413. if child.widget == w {
  414. return
  415. }
  416. }
  417. // Add it.
  418. s.widgets[s.serial] = WidgetSlot{
  419. id: s.serial,
  420. widget: w,
  421. }
  422. s.serial++
  423. s.lock.Unlock()
  424. }
  425. // PushModal sets the widget to be a "modal" for the Supervisor.
  426. //
  427. // Modal widgets have top-most event priority: mouse and click events go ONLY
  428. // to the modal and its descendants. Modals work as a stack: the most recently
  429. // pushed widget is the active modal, and popping the modal will make the
  430. // next most-recent widget be the active modal.
  431. //
  432. // If a Click event registers OUTSIDE the bounds of the modal widget, the
  433. // widget receives a CloseModal event.
  434. //
  435. // Returns the length of the modal stack.
  436. func (s *Supervisor) PushModal(w Widget) int {
  437. s.modals = append(s.modals, w)
  438. return len(s.modals)
  439. }
  440. // PopModal attempts to pop the modal from the stack, but only if the modal
  441. // is at the top of the stack.
  442. //
  443. // A widget may safely attempt to PopModal itself on a CloseModal event to
  444. // close themselves when the user clicks outside their box. If there were a
  445. // newer modal on the stack, this PopModal action would do nothing.
  446. func (s *Supervisor) PopModal(w Widget) bool {
  447. // only can pop if the topmost widget is the one being asked for
  448. if len(s.modals) > 0 && s.modals[len(s.modals)-1] == w {
  449. modal := s.modals[len(s.modals)-1]
  450. modal.Hide()
  451. // pop it off
  452. s.modals = s.modals[:len(s.modals)-1]
  453. return true
  454. }
  455. return false
  456. }
  457. // GetModal returns the modal on the top of the stack, or nil if there is
  458. // no modal on top.
  459. func (s *Supervisor) GetModal() Widget {
  460. if len(s.modals) == 0 {
  461. return nil
  462. }
  463. return s.modals[len(s.modals)-1]
  464. }