2
0
Эх сурвалжийг харах

Implemented press, focus and hover effects in menus. Renamed hover into hovered for themes.

David Piuva 2 жил өмнө
parent
commit
4ada2d4d99

+ 19 - 7
Source/DFPSR/gui/VisualComponent.cpp

@@ -379,6 +379,10 @@ bool VisualComponent::pointIsInsideOfOverlay(const IVector2D& pixelPosition) {
 	return false;
 	return false;
 }
 }
 
 
+bool VisualComponent::pointIsInsideOfHover(const IVector2D& pixelPosition) {
+	return this->pointIsInside(pixelPosition);
+}
+
 // Non-recursive top-down search
 // Non-recursive top-down search
 std::shared_ptr<VisualComponent> VisualComponent::getDirectChild(const IVector2D& pixelPosition) {
 std::shared_ptr<VisualComponent> VisualComponent::getDirectChild(const IVector2D& pixelPosition) {
 	// Iterate child components in reverse drawing order
 	// Iterate child components in reverse drawing order
@@ -487,7 +491,7 @@ void VisualComponent::addStateBits(ComponentState directStates, bool unique) {
 	VisualComponent *root = getRoot(this);
 	VisualComponent *root = getRoot(this);
 	// Remove all focus in the window if unique.
 	// Remove all focus in the window if unique.
 	if (unique) root->applyStateAndMask(~directStates);
 	if (unique) root->applyStateAndMask(~directStates);
-	// Apply focus directly to itself and indirectly to parents.
+	// Apply state directly to itself and indirectly to parents.
 	this->currentState |= directStates;
 	this->currentState |= directStates;
 	// Update indirect states, so that parent components know what happens to their child components.
 	// Update indirect states, so that parent components know what happens to their child components.
 	root->updateIndirectStates();
 	root->updateIndirectStates();
@@ -495,7 +499,7 @@ void VisualComponent::addStateBits(ComponentState directStates, bool unique) {
 
 
 void VisualComponent::removeStateBits(ComponentState directStates) {
 void VisualComponent::removeStateBits(ComponentState directStates) {
 	VisualComponent *root = getRoot(this);
 	VisualComponent *root = getRoot(this);
-	// Apply focus directly to itself and indirectly to parents.
+	// Remove state directly from itself and indirectly from parents.
 	this->currentState &= ~directStates;
 	this->currentState &= ~directStates;
 	// Update indirect states, so that parent components know what happens to their child components.
 	// Update indirect states, so that parent components know what happens to their child components.
 	root->updateIndirectStates();
 	root->updateIndirectStates();
@@ -511,6 +515,10 @@ void VisualComponent::hover() {
 	this->addStateBits(componentState_hoverDirect, true);
 	this->addStateBits(componentState_hoverDirect, true);
 }
 }
 
 
+void VisualComponent::leave() {
+	this->removeStateBits(componentState_hoverDirect);
+}
+
 void VisualComponent::showOverlay() {
 void VisualComponent::showOverlay() {
 	this->addStateBits(componentState_showingOverlayDirect, false);
 	this->addStateBits(componentState_showingOverlayDirect, false);
 }
 }
@@ -578,7 +586,11 @@ void VisualComponent::sendMouseEvent(const MouseEvent& event, bool recursive) {
 		MouseEvent parentEvent = event;
 		MouseEvent parentEvent = event;
 		parentEvent.position += this->location.upperLeft();
 		parentEvent.position += this->location.upperLeft();
 		// Itself is directly hovered.
 		// Itself is directly hovered.
-		this->hover();
+		if (pointIsInsideOfHover(parentEvent.position)) {
+			this->hover();
+		} else {
+			this->leave();
+		}
 		// If the event receiver pass it on to child components, it can just reset the hover flags again.
 		// If the event receiver pass it on to child components, it can just reset the hover flags again.
 		this->receiveMouseEvent(parentEvent);
 		this->receiveMouseEvent(parentEvent);
 	}
 	}
@@ -661,8 +673,8 @@ bool VisualComponent::managesChildren() {
 	return false;
 	return false;
 }
 }
 
 
-MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed, int focused, int hover) {
-	return method.callUsingKeywords([&theme, &method, width, height, red, green, blue, pressed, focused, hover](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName) {
+MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed, int focused, int hovered) {
+	return method.callUsingKeywords([&theme, &method, width, height, red, green, blue, pressed, focused, hovered](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName) {
 		if (string_caseInsensitiveMatch(argumentName, U"width")) {
 		if (string_caseInsensitiveMatch(argumentName, U"width")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, width);
 			machine_setInputByIndex(machine, methodIndex, inputIndex, width);
 		} else if (string_caseInsensitiveMatch(argumentName, U"height")) {
 		} else if (string_caseInsensitiveMatch(argumentName, U"height")) {
@@ -671,8 +683,8 @@ MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method,
 			machine_setInputByIndex(machine, methodIndex, inputIndex, pressed);
 			machine_setInputByIndex(machine, methodIndex, inputIndex, pressed);
 		} else if (string_caseInsensitiveMatch(argumentName, U"focused")) {
 		} else if (string_caseInsensitiveMatch(argumentName, U"focused")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, focused);
 			machine_setInputByIndex(machine, methodIndex, inputIndex, focused);
-		} else if (string_caseInsensitiveMatch(argumentName, U"hover")) {
-			machine_setInputByIndex(machine, methodIndex, inputIndex, hover);
+		} else if (string_caseInsensitiveMatch(argumentName, U"hovered")) {
+			machine_setInputByIndex(machine, methodIndex, inputIndex, hovered);
 		} else if (string_caseInsensitiveMatch(argumentName, U"red")) {
 		} else if (string_caseInsensitiveMatch(argumentName, U"red")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, red);
 			machine_setInputByIndex(machine, methodIndex, inputIndex, red);
 		} else if (string_caseInsensitiveMatch(argumentName, U"green")) {
 		} else if (string_caseInsensitiveMatch(argumentName, U"green")) {

+ 5 - 1
Source/DFPSR/gui/VisualComponent.h

@@ -36,7 +36,7 @@
 namespace dsr {
 namespace dsr {
 
 
 // A reusable method for calling the media machine that allow providing additional variables as style flags.
 // A reusable method for calling the media machine that allow providing additional variables as style flags.
-MediaResult component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed = 0, int focused = 0, int hover = 0);
+MediaResult component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed = 0, int focused = 0, int hovered = 0);
 
 
 class VisualComponent : public Persistent {
 class VisualComponent : public Persistent {
 PERSISTENT_DECLARATION(VisualComponent)
 PERSISTENT_DECLARATION(VisualComponent)
@@ -93,6 +93,8 @@ public: // Hover is reset before assigned again using a bit mask.
 	bool ownsHover() { return (this->currentState & (componentState_hoverDirect | componentState_hoverIndirect)) != 0; }
 	bool ownsHover() { return (this->currentState & (componentState_hoverDirect | componentState_hoverIndirect)) != 0; }
 	// Make the component directly hovered and its parents indirectly hovered.
 	// Make the component directly hovered and its parents indirectly hovered.
 	void hover();
 	void hover();
+	// Remove the hover effect.
+	void leave();
 public: // Showing overlay
 public: // Showing overlay
 	inline bool showingOverlay() { return (this->currentState & componentState_showingOverlayDirect) != 0; }
 	inline bool showingOverlay() { return (this->currentState & componentState_showingOverlayDirect) != 0; }
 	inline bool ownsOverlay() { return (this->currentState & componentState_showingOverlay) != 0; }
 	inline bool ownsOverlay() { return (this->currentState & componentState_showingOverlay) != 0; }
@@ -239,6 +241,8 @@ public:
 	// Returns true iff the pixelPosition relative to the parent container's upper left corner is inside of the component's overlay.
 	// Returns true iff the pixelPosition relative to the parent container's upper left corner is inside of the component's overlay.
 	// The caller is responsible for checking if the component is showing an overlay (this->showOverlay).
 	// The caller is responsible for checking if the component is showing an overlay (this->showOverlay).
 	virtual bool pointIsInsideOfOverlay(const IVector2D& pixelPosition);
 	virtual bool pointIsInsideOfOverlay(const IVector2D& pixelPosition);
+	// Return true iff the pixelPosition relative to the parent container's upper left corner is inside of the region causing a hover effect.
+	virtual bool pointIsInsideOfHover(const IVector2D& pixelPosition);
 	// Send a mouse down event to the component
 	// Send a mouse down event to the component
 	//   pixelPosition is relative to the component's own upper left corner. TODO: Does this make sense, or should it use parent coordinates?
 	//   pixelPosition is relative to the component's own upper left corner. TODO: Does this make sense, or should it use parent coordinates?
 	//   The component is reponsible for bound checking, which can be used to either block the signal or pass to components below.
 	//   The component is reponsible for bound checking, which can be used to either block the signal or pass to components below.

+ 13 - 0
Source/DFPSR/gui/VisualTheme.cpp

@@ -465,4 +465,17 @@ bool theme_assignMediaMachineArguments(const VisualTheme &theme, int contextInde
 	                         || assignMediaMachineArguments(theme->settings[0],            machine, methodIndex, inputIndex, argumentName);
 	                         || assignMediaMachineArguments(theme->settings[0],            machine, methodIndex, inputIndex, argumentName);
 }
 }
 
 
+ComponentState theme_getStateListenerMask(const MediaMethod &scalableImage) {
+	ComponentState result = 0;
+	for (int inputIndex = 0; inputIndex < machine_getInputCount(scalableImage.machine, scalableImage.methodIndex); inputIndex++) {
+		String upperInputName = string_upperCase(machine_getInputName(scalableImage.machine, scalableImage.methodIndex, inputIndex));
+		if (string_match(upperInputName, U"FOCUSED")) {
+			result |= componentState_focusDirect;
+		} else if (string_match(upperInputName, U"HOVERED")) {
+			result |= componentState_hoverDirect;
+		}
+	}
+	return result;
+}
+
 }
 }

+ 7 - 0
Source/DFPSR/gui/VisualTheme.h

@@ -25,6 +25,7 @@
 #define DFPSR_GUI_VISUALTHEME
 #define DFPSR_GUI_VISUALTHEME
 
 
 #include "../api/mediaMachineAPI.h"
 #include "../api/mediaMachineAPI.h"
+#include "componentStates.h"
 
 
 namespace dsr {
 namespace dsr {
 
 
@@ -92,6 +93,12 @@ ReadableString theme_getString(const VisualTheme &theme, const ReadableString &c
 // Post-condition: Returns true if argumentName was identified and assigned as input to inputIndex of methodIndex in machine.
 // Post-condition: Returns true if argumentName was identified and assigned as input to inputIndex of methodIndex in machine.
 bool theme_assignMediaMachineArguments(const VisualTheme &theme, int contextIndex, MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName);
 bool theme_assignMediaMachineArguments(const VisualTheme &theme, int contextIndex, MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName);
 
 
+// Post-condition:
+//   Returns a bit-mask for the direct states that have corresponding input arguments in the method.
+//     Includes the componentState_focusDirect bit if the method has an input argument named "focused".
+//     Includes the componentState_hoverDirect bit if the method has an input argument named "hover".
+ComponentState theme_getStateListenerMask(const MediaMethod &scalableImage);
+
 }
 }
 
 
 #endif
 #endif

+ 67 - 16
Source/DFPSR/gui/components/Menu.cpp

@@ -79,10 +79,10 @@ bool Menu::hasArrow() {
 	return this->subMenu && this->getChildCount() > 0;
 	return this->subMenu && this->getChildCount() > 0;
 }
 }
 
 
-static OrderedImageRgbaU8 generateHeadImage(Menu &menu, MediaMethod imageGenerator, int pressed, int width, int height, ColorRgbI32 backColor, ColorRgbI32 foreColor, const ReadableString &text, RasterFont font) {
+static OrderedImageRgbaU8 generateHeadImage(Menu &menu, MediaMethod imageGenerator, int pressed, int focused, int hover, int width, int height, ColorRgbI32 backColor, ColorRgbI32 foreColor, const ReadableString &text, RasterFont font) {
 	// Create a scaled image
 	// Create a scaled image
 	OrderedImageRgbaU8 result;
 	OrderedImageRgbaU8 result;
- 	component_generateImage(menu.getTheme(), imageGenerator, width, height, backColor.red, backColor.green, backColor.blue, pressed)(result);
+ 	component_generateImage(menu.getTheme(), imageGenerator, width, height, backColor.red, backColor.green, backColor.blue, pressed, focused, hover)(result);
 	if (string_length(text) > 0) {
 	if (string_length(text) > 0) {
 		int backWidth = image_getWidth(result);
 		int backWidth = image_getWidth(result);
 		int backHeight = image_getHeight(result);
 		int backHeight = image_getHeight(result);
@@ -110,11 +110,13 @@ void Menu::generateGraphics() {
 	int headHeight = this->location.height();
 	int headHeight = this->location.height();
 	if (headWidth < 1) { headWidth = 1; }
 	if (headWidth < 1) { headWidth = 1; }
 	if (headHeight < 1) { headHeight = 1; }
 	if (headHeight < 1) { headHeight = 1; }
-	if (!this->hasImages) {
+	// headImage is set to an empty handle when something used as input changes.
+	if (!image_exists(this->headImage)) {
 		completeAssets();
 		completeAssets();
-		this->imageUp = generateHeadImage(*this, this->headImageMethod, 0, headWidth, headHeight, this->backColor.value, this->foreColor.value, this->text.value, this->font);
-		this->imageDown = generateHeadImage(*this, this->headImageMethod, 0, headWidth, headHeight, ColorRgbI32(0, 0, 0), ColorRgbI32(255, 255, 255), this->text.value, this->font);
-		this->hasImages = true;
+		bool focus = this->isFocused();
+		bool hover = this->isHovered();
+		bool press = this->pressed && hover;
+		this->headImage = generateHeadImage(*this, this->headImageMethod, press, focus, hover, headWidth, headHeight, this->backColor.value, this->foreColor.value, this->text.value, this->font);
 	}
 	}
 }
 }
 
 
@@ -122,9 +124,9 @@ void Menu::generateGraphics() {
 void Menu::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
 void Menu::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
 	this->generateGraphics();
 	this->generateGraphics();
 	if (this->menuHead_filter == 1) {
 	if (this->menuHead_filter == 1) {
-		draw_alphaFilter(targetImage, this->showingOverlay() ? this->imageDown : this->imageUp, relativeLocation.left(), relativeLocation.top());
+		draw_alphaFilter(targetImage, this->headImage, relativeLocation.left(), relativeLocation.top());
 	} else {
 	} else {
-		draw_copy(targetImage, this->showingOverlay() ? this->imageDown : this->imageUp, relativeLocation.left(), relativeLocation.top());
+		draw_copy(targetImage, this->headImage, relativeLocation.left(), relativeLocation.top());
 	}
 	}
 }
 }
 
 
@@ -178,6 +180,8 @@ void Menu::loadTheme(const VisualTheme &theme) {
 	this->finalHeadClass = theme_selectClass(theme, this->headClass.value, this->subMenu ? U"MenuSub" : U"MenuTop");
 	this->finalHeadClass = theme_selectClass(theme, this->headClass.value, this->subMenu ? U"MenuSub" : U"MenuTop");
 	this->finalListClass = theme_selectClass(theme, this->listClass.value, U"MenuList");
 	this->finalListClass = theme_selectClass(theme, this->listClass.value, U"MenuList");
 	this->headImageMethod = theme_getScalableImage(theme, this->finalHeadClass);
 	this->headImageMethod = theme_getScalableImage(theme, this->finalHeadClass);
+	// Check which states the scalable head image is listening to.
+	this->headStateListenerMask = theme_getStateListenerMask(this->headImageMethod);
 	this->listBackgroundImageMethod = theme_getScalableImage(theme, this->finalListClass);
 	this->listBackgroundImageMethod = theme_getScalableImage(theme, this->finalListClass);
 	// Ask the theme which parts should be drawn using alpha filtering, and fall back on solid drawing.
 	// Ask the theme which parts should be drawn using alpha filtering, and fall back on solid drawing.
 	this->menuHead_filter = theme_getInteger(theme, this->finalHeadClass, U"Filter", 0);
 	this->menuHead_filter = theme_getInteger(theme, this->finalHeadClass, U"Filter", 0);
@@ -186,7 +190,7 @@ void Menu::loadTheme(const VisualTheme &theme) {
 
 
 void Menu::changedTheme(VisualTheme newTheme) {
 void Menu::changedTheme(VisualTheme newTheme) {
 	this->loadTheme(newTheme);
 	this->loadTheme(newTheme);
-	this->hasImages = false;
+	this->headImage = OrderedImageRgbaU8();
 }
 }
 
 
 void Menu::completeAssets() {
 void Menu::completeAssets() {
@@ -201,7 +205,7 @@ void Menu::completeAssets() {
 void Menu::changedLocation(const IRect &oldLocation, const IRect &newLocation) {
 void Menu::changedLocation(const IRect &oldLocation, const IRect &newLocation) {
 	// If the component has changed dimensions then redraw the image
 	// If the component has changed dimensions then redraw the image
 	if (oldLocation.size() != newLocation.size()) {
 	if (oldLocation.size() != newLocation.size()) {
-		this->hasImages = false;
+		this->headImage = OrderedImageRgbaU8();
 	}
 	}
 }
 }
 
 
@@ -211,7 +215,7 @@ void Menu::changedAttribute(const ReadableString &name) {
 		// Update from the theme if a theme class has changed.
 		// Update from the theme if a theme class has changed.
 		this->changedTheme(this->getTheme());
 		this->changedTheme(this->getTheme());
 	} else if (!string_caseInsensitiveMatch(name, U"Visible")) {
 	} else if (!string_caseInsensitiveMatch(name, U"Visible")) {
-		this->hasImages = false;
+		this->headImage = OrderedImageRgbaU8();
 	}
 	}
 	VisualComponent::changedAttribute(name);
 	VisualComponent::changedAttribute(name);
 }
 }
@@ -228,6 +232,22 @@ void Menu::updateStateEvent(ComponentState oldState, ComponentState newState) {
 		// Clean up the background image to save memory and allow it to be regenerated in another size later.
 		// Clean up the background image to save memory and allow it to be regenerated in another size later.
 		this->listBackgroundImage = OrderedImageRgbaU8();
 		this->listBackgroundImage = OrderedImageRgbaU8();
 	}
 	}
+	// Check which states have changed.
+	ComponentState changedStates = newState ^ oldState;
+	// TODO: Debug print to see if this works and create themes that respond to direct focus and hover states using color modifications as a simple start.
+	// Check if any of the changed bits overlap with the states that the head's scalable image generator uses as input.
+	if (changedStates & this->headStateListenerMask) {
+		// If a state affecting the input has changed, the image should be updated.
+		// The pressed argument can also be requested by the scalable images, but that is handled by components themselves.
+		this->headImage = OrderedImageRgbaU8();
+	}
+	// When pressed, changes in hover will affect if the component will appear pressed,
+	//   so show that one can safely abort a press done by mistake by releasing outside.
+	if (this->pressed && (changedStates & componentState_hoverDirect)) {
+		// If a state affecting the input has changed, the image should be updated.
+		// The pressed argument can also be requested by the scalable images, but that is handled by components themselves.
+		this->headImage = OrderedImageRgbaU8();
+	}
 }
 }
 
 
 void Menu::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {	
 void Menu::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {	
@@ -266,17 +286,32 @@ void Menu::receiveMouseEvent(const MouseEvent& event) {
 	int childCount = this->getChildCount();
 	int childCount = this->getChildCount();
 	MouseEvent localEvent = event;
 	MouseEvent localEvent = event;
 	localEvent.position -= this->location.upperLeft();
 	localEvent.position -= this->location.upperLeft();
-	if (this->showingOverlay() && this->pointIsInsideOfOverlay(event.position)) {
+	bool inOverlay = this->showingOverlay() && this->pointIsInsideOfOverlay(event.position);
+	bool inHead = this->pointIsInside(event.position);
+	if (event.mouseEventType == MouseEventType::MouseUp) {
+		// Pass on mouse up events to dragged components, even if not inside of them.
+		if (this->dragComponent.get() != nullptr) {
+			MouseEvent childEvent = localEvent;
+			childEvent.position -= this->dragComponent->location.upperLeft();
+			this->dragComponent->sendMouseEvent(childEvent, true);
+		}
+	} else if (inOverlay) {
+		// Pass on down and move events to a child component that the cursor is inside of.
 		for (int i = childCount - 1; i >= 0; i--) {
 		for (int i = childCount - 1; i >= 0; i--) {
 			if (this->children[i]->pointIsInside(localEvent.position)) {
 			if (this->children[i]->pointIsInside(localEvent.position)) {
-				this->children[i]->makeFocused();
 				MouseEvent childEvent = localEvent;
 				MouseEvent childEvent = localEvent;
 				childEvent.position -= this->children[i]->location.upperLeft();
 				childEvent.position -= this->children[i]->location.upperLeft();
+				if (event.mouseEventType == MouseEventType::MouseDown) {
+					this->dragComponent = this->children[i];
+					this->dragComponent->makeFocused();
+				}
 				this->children[i]->sendMouseEvent(childEvent, true);
 				this->children[i]->sendMouseEvent(childEvent, true);
 				break;
 				break;
 			}
 			}
 		}
 		}
-	} else if (this->pointIsInside(event.position)) {
+	}
+	// If not interacting with the overlay and the cursor is within the head.
+	if (!inOverlay && inHead) {
 		if (childCount > 0) { // Has a list of members to open, toggle expansion when clicked.
 		if (childCount > 0) { // Has a list of members to open, toggle expansion when clicked.
 			if (this->subMenu) { // Menu within another menu.
 			if (this->subMenu) { // Menu within another menu.
 				// Hover to expand sub-menu's list.
 				// Hover to expand sub-menu's list.
@@ -313,16 +348,32 @@ void Menu::receiveMouseEvent(const MouseEvent& event) {
 			}
 			}
 		} else { // List item, because it has no children.
 		} else { // List item, because it has no children.
 			// Childless menu components are treated as menu items that can be clicked to perform an action and close the menu.
 			// Childless menu components are treated as menu items that can be clicked to perform an action and close the menu.
-			if (event.mouseEventType == MouseEventType::MouseDown) {
+			if ((event.mouseEventType == MouseEventType::MouseDown) && !this->pressed) {
+				// Show that the event is about to be triggered.
+				this->pressed = true;
+				// Update the head image.
+				this->headImage = OrderedImageRgbaU8();
+			} else if ((event.mouseEventType == MouseEventType::MouseUp) && this->pressed) {
+				// Released a press inside, confirming the event.
 				// Hide overlays all the way to root.
 				// Hide overlays all the way to root.
 				closeEntireMenu(this);
 				closeEntireMenu(this);
 				// Call the event assigned to this menu item.
 				// Call the event assigned to this menu item.
 				this->callback_pressedEvent();
 				this->callback_pressedEvent();
 			}
 			}
 		}
 		}
-		// Because the main body was interacted with, the mouse events are passed on.
+		// Because the main body was interacted with, the basic up/down/move/scroll mouse events are triggered.
 		VisualComponent::receiveMouseEvent(event);
 		VisualComponent::receiveMouseEvent(event);
 	}
 	}
+	// Releasing anywhere should stop pressing.
+	if (event.mouseEventType == MouseEventType::MouseUp) {
+		this->dragComponent = std::shared_ptr<VisualComponent>();
+		if (this->pressed) {
+			// No longer pressed.
+			this->pressed = false;
+			// Update the head image.
+			this->headImage = OrderedImageRgbaU8();
+		}
+	}
 }
 }
 
 
 IVector2D Menu::getDesiredDimensions() {
 IVector2D Menu::getDesiredDimensions() {

+ 2 - 4
Source/DFPSR/gui/components/Menu.h

@@ -55,18 +55,16 @@ private:
 	void generateBackground();
 	void generateBackground();
 	MediaMethod headImageMethod, listBackgroundImageMethod;
 	MediaMethod headImageMethod, listBackgroundImageMethod;
 	OrderedImageRgbaU8 headImage, listBackgroundImage;
 	OrderedImageRgbaU8 headImage, listBackgroundImage;
+	ComponentState headStateListenerMask; // Bit-mask telling which state bits are requested as input arguments to headImageMethod.
 	RasterFont font;
 	RasterFont font;
 	bool subMenu = false;
 	bool subMenu = false;
 	IRect overlayLocation; // Relative to the parent's location, just like its own location
 	IRect overlayLocation; // Relative to the parent's location, just like its own location
+	bool pressed = false;
 	// Settings fetched from the theme
 	// Settings fetched from the theme
 	String finalHeadClass; // The selected HeadClass/Class from layout settings or the component's default theme class "MenuTop" or "MenuSub" based on ownership.
 	String finalHeadClass; // The selected HeadClass/Class from layout settings or the component's default theme class "MenuTop" or "MenuSub" based on ownership.
 	String finalListClass; // The selected ListClass from layout settings or the component's default theme class "MenuList".
 	String finalListClass; // The selected ListClass from layout settings or the component's default theme class "MenuList".
 	int menuHead_filter = 0; // 0 for solid, 1 for alpha filter.
 	int menuHead_filter = 0; // 0 for solid, 1 for alpha filter.
 	int menuList_filter = 0; // 0 for solid, 1 for alpha filter.
 	int menuList_filter = 0; // 0 for solid, 1 for alpha filter.
-	// Generated
-	bool hasImages = false;
-	OrderedImageRgbaU8 imageUp;
-	OrderedImageRgbaU8 imageDown;
 public:
 public:
 	Menu();
 	Menu();
 public:
 public:

+ 52 - 0
Source/SDK/guiExample/media/Drawing.mmc

@@ -116,6 +116,40 @@ BEGIN: FocusableDiffuse3x3
 	PACK_RGBA: colorImage, redImage, greenImage, blueImage, visibilityMap
 	PACK_RGBA: colorImage, redImage, greenImage, blueImage, visibilityMap
 END:
 END:
 
 
+BEGIN: PressableDiffuse3x3
+	INPUT: FixedPoint, width
+	INPUT: FixedPoint, height
+	INPUT: FixedPoint, red
+	INPUT: FixedPoint, green
+	INPUT: FixedPoint, blue
+	INPUT: FixedPoint, pressed
+	INPUT: FixedPoint, focused
+	INPUT: FixedPoint, hovered
+	INPUT: FixedPoint, sourceLeft
+	INPUT: FixedPoint, sourceTop
+	INPUT: FixedPoint, sourceWidth
+	INPUT: FixedPoint, sourceHeight
+	INPUT: FixedPoint, pressOffsetX
+	INPUT: FixedPoint, pressOffsetY
+	INPUT: FixedPoint, preserved
+	INPUT: ImageRgbaU8, atlas
+	OUTPUT: ImageRgbaU8, colorImage
+	# Select image using the pressed state.
+	MUL: sourceOffsetX<FixedPoint>, pressed, pressOffsetX
+	MUL: sourceOffsetY<FixedPoint>, pressed, pressOffsetY
+	ADD: adjustedSourceLeft<FixedPoint>, sourceLeft, sourceOffsetX
+	ADD: adjustedSourceTop<FixedPoint>, sourceTop, sourceOffsetY
+	MUL: hoverChange<FixedPoint>, hovered, 0.1
+	MUL: focusChange<FixedPoint>, focused, -0.2
+	ADD: intensity<FixedPoint>, 1.0, hoverChange
+	ADD: intensity, intensity, focusChange
+	Mul: red, red, intensity
+	Mul: green, green, intensity
+	Mul: blue, blue, intensity
+	# Rescale the source region to fit width and height, while applying the diffuse color and adding white shine.
+	CALL: Diffuse3x3, colorImage, width, height, red, green, blue, adjustedSourceLeft, adjustedSourceTop, sourceWidth, sourceHeight, preserved, atlas
+END:
+
 BEGIN: DiffuseSpecular3x3
 BEGIN: DiffuseSpecular3x3
 	INPUT: FixedPoint, width
 	INPUT: FixedPoint, width
 	INPUT: FixedPoint, height
 	INPUT: FixedPoint, height
@@ -154,6 +188,8 @@ BEGIN: PressableDiffuseSpecular3x3
 	INPUT: FixedPoint, green
 	INPUT: FixedPoint, green
 	INPUT: FixedPoint, blue
 	INPUT: FixedPoint, blue
 	INPUT: FixedPoint, pressed
 	INPUT: FixedPoint, pressed
+	INPUT: FixedPoint, focused
+	INPUT: FixedPoint, hovered
 	INPUT: FixedPoint, sourceLeft
 	INPUT: FixedPoint, sourceLeft
 	INPUT: FixedPoint, sourceTop
 	INPUT: FixedPoint, sourceTop
 	INPUT: FixedPoint, sourceWidth
 	INPUT: FixedPoint, sourceWidth
@@ -168,6 +204,13 @@ BEGIN: PressableDiffuseSpecular3x3
 	MUL: sourceOffsetY<FixedPoint>, pressed, pressOffsetY
 	MUL: sourceOffsetY<FixedPoint>, pressed, pressOffsetY
 	ADD: adjustedSourceLeft<FixedPoint>, sourceLeft, sourceOffsetX
 	ADD: adjustedSourceLeft<FixedPoint>, sourceLeft, sourceOffsetX
 	ADD: adjustedSourceTop<FixedPoint>, sourceTop, sourceOffsetY
 	ADD: adjustedSourceTop<FixedPoint>, sourceTop, sourceOffsetY
+	MUL: hoverChange<FixedPoint>, hovered, 0.1
+	MUL: focusChange<FixedPoint>, focused, -0.2
+	ADD: intensity<FixedPoint>, 1.0, hoverChange
+	ADD: intensity, intensity, focusChange
+	Mul: red, red, intensity
+	Mul: green, green, intensity
+	Mul: blue, blue, intensity
 	# Rescale the source region to fit width and height, while applying the diffuse color and adding white shine.
 	# Rescale the source region to fit width and height, while applying the diffuse color and adding white shine.
 	CALL: DiffuseSpecular3x3, colorImage, width, height, red, green, blue, adjustedSourceLeft, adjustedSourceTop, sourceWidth, sourceHeight, preserved, atlas
 	CALL: DiffuseSpecular3x3, colorImage, width, height, red, green, blue, adjustedSourceLeft, adjustedSourceTop, sourceWidth, sourceHeight, preserved, atlas
 END:
 END:
@@ -210,6 +253,8 @@ BEGIN: PressableDiffuseSpecular1x1
 	INPUT: FixedPoint, green
 	INPUT: FixedPoint, green
 	INPUT: FixedPoint, blue
 	INPUT: FixedPoint, blue
 	INPUT: FixedPoint, pressed
 	INPUT: FixedPoint, pressed
+	INPUT: FixedPoint, focused
+	INPUT: FixedPoint, hovered
 	INPUT: FixedPoint, sourceLeft
 	INPUT: FixedPoint, sourceLeft
 	INPUT: FixedPoint, sourceTop
 	INPUT: FixedPoint, sourceTop
 	INPUT: FixedPoint, sourceWidth
 	INPUT: FixedPoint, sourceWidth
@@ -224,6 +269,13 @@ BEGIN: PressableDiffuseSpecular1x1
 	MUL: sourceOffsetY<FixedPoint>, pressed, pressOffsetY
 	MUL: sourceOffsetY<FixedPoint>, pressed, pressOffsetY
 	ADD: adjustedSourceLeft<FixedPoint>, sourceLeft, sourceOffsetX
 	ADD: adjustedSourceLeft<FixedPoint>, sourceLeft, sourceOffsetX
 	ADD: adjustedSourceTop<FixedPoint>, sourceTop, sourceOffsetY
 	ADD: adjustedSourceTop<FixedPoint>, sourceTop, sourceOffsetY
+	MUL: hoverChange<FixedPoint>, hovered, 0.1
+	MUL: focusChange<FixedPoint>, focused, -0.2
+	ADD: intensity<FixedPoint>, 1.0, hoverChange
+	ADD: intensity, intensity, focusChange
+	Mul: red, red, intensity
+	Mul: green, green, intensity
+	Mul: blue, blue, intensity
 	# Rescale the source region to fit width and height, while applying the diffuse color and adding white shine.
 	# Rescale the source region to fit width and height, while applying the diffuse color and adding white shine.
 	CALL: DiffuseSpecular1x1, colorImage, width, height, red, green, blue, adjustedSourceLeft, adjustedSourceTop, sourceWidth, sourceHeight, preserved, atlas
 	CALL: DiffuseSpecular1x1, colorImage, width, height, red, green, blue, adjustedSourceLeft, adjustedSourceTop, sourceWidth, sourceHeight, preserved, atlas
 END:
 END:

BIN
Source/SDK/guiExample/media/Style.png


BIN
Source/SDK/guiExample/media/Style.xcf


+ 12 - 8
Source/SDK/guiExample/media/Theme.ini

@@ -101,27 +101,31 @@ sourceWidth = 16
 sourceHeight = 16
 sourceHeight = 16
 
 
 [MenuTop]
 [MenuTop]
-method = "Diffuse3x3"
+method = "PressableDiffuse3x3"
 sourceLeft = 32
 sourceLeft = 32
 sourceTop = 16
 sourceTop = 16
 sourceWidth = 16
 sourceWidth = 16
 sourceHeight = 16
 sourceHeight = 16
+pressOffsetX = 16
+pressOffsetY = 0
 preserved = 7
 preserved = 7
 filter = 1
 filter = 1
 
 
-[MenuList]
-method = "Diffuse3x3"
-sourceLeft = 48
-sourceTop = 16
+[MenuSub]
+method = "PressableDiffuse3x3"
+sourceLeft = 32
+sourceTop = 32
 sourceWidth = 16
 sourceWidth = 16
 sourceHeight = 16
 sourceHeight = 16
+pressOffsetX = 16
+pressOffsetY = 0
+preserved = 7
 filter = 1
 filter = 1
 
 
-[MenuSub]
+[MenuList]
 method = "Diffuse3x3"
 method = "Diffuse3x3"
 sourceLeft = 32
 sourceLeft = 32
-sourceTop = 16
+sourceTop = 48
 sourceWidth = 16
 sourceWidth = 16
 sourceHeight = 16
 sourceHeight = 16
-preserved = 7
 filter = 1
 filter = 1