view mde/gui/WMScreen.d @ 122:f96e8d18c00a

Missed file from last commit.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 02 Jan 2009 18:10:14 +0000
parents
children c9843fbaac88
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 || 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);
	}
    }
}