# HG changeset patch # User Diggory Hardy # Date 1230919814 0 # Node ID f96e8d18c00a6b3f4607780613c0c67e73e8dbcc # Parent 5b37d040073227efe7dd16e5aea4c186489a1ac1 Missed file from last commit. diff -r 5b37d0400732 -r f96e8d18c00a mde/gui/WMScreen.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/WMScreen.d Fri Jan 02 18:10:14 2009 +0000 @@ -0,0 +1,228 @@ +/* LICENSE BLOCK +Part of mde: a Modular D game-oriented Engine +Copyright © 2007-2008 Diggory Hardy + +This program is free software: you can redistribute it and/or modify it under the terms +of the GNU General Public License as published by the Free Software Foundation, either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . */ + +/************************************************************************************************* + * A gui manager class using mde.setup.Screen and mde.input.Input. + * + * This is the module to use externally to create a graphical user interface (likely also with + * content modules). + *************************************************************************************************/ +module mde.gui.WMScreen; + +import mde.gui.WidgetManager; +import mde.gui.widget.Ifaces; +import mde.gui.renderer.createRenderer; + +import mde.setup.Screen; +import Items = mde.content.Items; // loadTranslation +import mde.lookup.Options; // miscOpts.L10n callback + +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.WMScreen"); +} + +/************************************************************************************************* + * The widget manager. + * + * This provides a layer on top of WidgetLoader, handling input and rendering. Other functionality + * is contained in the super class, to simplify supporting new input/graphics libraries. + * + * Currently mouse coordinates are passed to widgets untranslated. It may make sense to translate + * them and possibly drop events for some uses, such as if the gui is drawn to a texture. + * + * Aside from the IWidgetManager methods, this class should be thread-safe. + *************************************************************************************************/ +scope class WMScreen : AWidgetManager, Screen.IDrawable { + /** Construct a new widget manager. + * + * params: + * fileName = Name of file specifying the gui, excluding path and extension. + */ + this (char[] file) { + super(file); + + Screen.addDrawable (this); + clickCallbacks = new typeof(clickCallbacks); + motionCallbacks = new typeof(motionCallbacks); + } + + // this() runs during static this(), when imde.input doesn't exist. init() runs later. + void init () { + // Doesn't need a lock - cannot conflict with other class functions. + // Events we want to know about: + imde.input.addMouseClickCallback(&clickEvent); + imde.input.addMouseMotionCallback(&motionEvent); + + Items.loadTranslation (); + miscOpts.L10n.addCallback (&reloadStrings); + } + + + /** Draw the gui. */ + void draw() { + synchronized(mutex) { + if (child) + child.draw; + foreach (popup; popups) + popup.widget.draw(); + } + } + + /** For mouse click events. + * + * Sends the event on to the relevant windows and all click callbacks. */ + void clickEvent (ushort usx, ushort usy, ubyte b, bool state) { + debug scope (failure) + logger.warn ("clickEvent: failed!"); + mutex.lock; + scope(exit) mutex.unlock; + if (child is null) return; + + wdabs cx = cast(wdabs) usx, cy = cast(wdabs) usy; + + // 1. Callbacks have the highest priority recieving events (e.g. a button release) + foreach (dg; clickCallbacks) + if (dg (cx, cy, b, state)) return; + + // 2. Then pop-ups: close from top, depending on click pos + // Note: assumes each evaluated popup's parent is not under another still open popup. + // Also assumes popup's parent doesn't have other children in its box. + size_t removeTo = popups.length; + bool eventDone; // don't pass clickEvent + IChildWidget widg; // widget clicked on + foreach_reverse (i,popup; popups) with (popup) { + if (cx < x || cx >= x + w || + cy < y || cy >= y + h) { // on popup + if (parent.onSelf (cx, cy)) { + if (parent.popupParentClick()) removeTo = i; + eventDone = true; + break; + } else { + removeTo = i; + parent.popupClose; + } + } else { + widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy); + break; + } + } + if (removeTo < popups.length) { + requestRedraw; + popups = popups[0..removeTo]; + } + if (eventDone) + return; + + // 3. Then the main widget tree + debug assert (cx < child.width && cy < child.height, "WidgetManager: child doesn't cover whole area (code error)"); + if (widg is null) + widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy); + if (keyFocus && keyFocus !is widg) { + keyFocus.keyFocusLost; + keyFocus = null; + imde.input.setLetterCallback (null); + } + if (widg !is null) { + if (widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) { + keyFocus = widg; + imde.input.setLetterCallback (&widg.keyEvent); + } + } + } + + /** For mouse motion events. + * + * Sends the event on to all motion callbacks. */ + void motionEvent (ushort scx, ushort scy) { + debug scope (failure) + logger.warn ("motionEvent: failed!"); + mutex.lock; + scope(exit) mutex.unlock; + wdabs cx = cast(wdabs) scx, cy = cast(wdabs) scy; + foreach (dg; motionCallbacks) + dg (cx, cy); + + IChildWidget ohighlighted = highlighted; + foreach_reverse (popup; popups) with (popup) { + if (cx >= x && cx < x+w && cy >= y && cy < y+h) { + highlighted = widget.getWidget (cx,cy); + goto foundPopup; + } + } + highlighted = null; // not over a popup + foundPopup: + if (ohighlighted != highlighted) { + if (ohighlighted) + ohighlighted.highlight (false); + if (highlighted) + highlighted.highlight (true); + requestRedraw; + } + } + + + void sizeEvent (int nw, int nh) { // IDrawable function + mutex.lock; + scope(exit) mutex.unlock; + + w = cast(wdim) nw; + h = cast(wdim) nh; + + if (w < mw || h < mh) + logger.warn ("Minimal dimensions ({},{}) not met: ({},{}), but I cannot resize myself!",mw,mh,w,h); + + if (!child) return; // if not created yet. + child.setWidth (w, -1); + child.setHeight (h, -1); + child.setPosition (0,0); + } + +protected: + /* Second stage of widget loading. + * Note: sizeEvent should be called with window size before this. */ + final override void createRootWidget () { + // The renderer needs to be created on the first load, but not after this. + if (rend is null) + rend = createRenderer (rendName); + popups = null; + + debug (mdeWidgets) logger.trace ("Creating root widget..."); + child = makeWidget (this, "root"); + debug (mdeWidgets) logger.trace ("Setting up root widget..."); + child.setup (0, 3); + + mw = child.minWidth; + mh = child.minHeight; + if (w < mw || h < mh) + logger.warn ("Minimal dimensions ({},{}) not met: ({},{}), but I cannot resize myself!",mw,mh,w,h); + + debug (mdeWidgets) logger.trace ("Setting size and position of root widget..."); + child.setWidth (w, -1); + child.setHeight (h, -1); + child.setPosition (0,0); + debug (mdeWidgets) logger.trace ("Done creating root widget."); + } + + final override void preSave () { + if (keyFocus) { + keyFocus.keyFocusLost; + keyFocus = null; + imde.input.setLetterCallback (null); + } + } +}