view mde/gui/WMScreen.d @ 126:c9843fbaac88

Dynamic minimal size changing improved; works over layouts sharing alignment. EnumContent sub-contents use EnumValueContent instead of BoolContent; fixes a few small bugs. EnumContent substrings get translated (bug fixed). The widget manager no longer attempts to set widget sizes smaller than their minimals, even though some will not be shown. SwitchWidget: has fixed sizableness now.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 08 Jan 2009 13:05:44 +0000
parents f96e8d18c00a
children 41582439a42b
line wrap: on
line source

/* 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 <http://www.gnu.org/licenses/>. */

/*************************************************************************************************
 * 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) {
            logger.warn ("Min width for gui, {}, not met: {}", mw, w);
            w = mw;
        }
        if (h < mh) {
            logger.warn ("Min height for gui, {}, not met: {}", mh, h);
            h = mh;
        }
        
        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) {
            logger.warn ("Min width for gui, {}, not met: {}", mw, w);
            w = mw;
        }
        if (h < mh) {
            logger.warn ("Min height for gui, {}, not met: {}", mh, h);
            h = mh;
        }
        
        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);
	}
    }
}