From 191c85a63f96573287ce0d2173026f6646c3898c Mon Sep 17 00:00:00 2001
From: jsalmon3 <>
Date: Sun, 23 May 2004 19:16:32 +0000
Subject: [PATCH] Pie menus from feb

---
 doc/scripts/ui.html        |  12 +++
 src/include/cursor.h       |   1 +
 src/include/interface.h    |   5 ++
 src/include/ui.h           |   6 ++
 src/stratagus/mainloop.cpp |   1 +
 src/ui/botpanel.cpp        |  58 +++++++-----
 src/ui/icons.cpp           |   2 +-
 src/ui/mouse.cpp           | 179 +++++++++++++++++++++++++++++++++++--
 src/ui/script_ui.cpp       |  51 +++++++++++
 src/ui/ui.cpp              |   8 ++
 10 files changed, 292 insertions(+), 31 deletions(-)

diff --git a/doc/scripts/ui.html b/doc/scripts/ui.html
index 4f082932f..81cc0accc 100644
--- a/doc/scripts/ui.html
+++ b/doc/scripts/ui.html
@@ -894,6 +894,18 @@ FIXME
   </dd>
 </dl>
 </dd>
+<dt>"pie-menu", {tag, value, ...}</dt>
+<dd>
+<dl>
+  <dt>"radius", radius</dt>
+  <dd>The radius in pixels of the pie menu.</dd>
+  <dt>"file", "filename"</dt>
+  <dd>The image file for the background of the pie menu.</dd>
+  <dt>"mouse-button", "buttonname"</dt>
+  <dd>Which mouse button pops up the pie menu. Can be "right", "middle" or "left".
+  </dd>
+</dl>
+</dd>
 <dt>"map-area", {"pos", {x, y}, size, {w, h}}</dt>
 <dd>FIXME</dd>
 <dt>"menu-panel", {tag, value}</dt>
diff --git a/src/include/cursor.h b/src/include/cursor.h
index 99ae2144e..73de66b26 100644
--- a/src/include/cursor.h
+++ b/src/include/cursor.h
@@ -166,6 +166,7 @@ typedef enum _cursor_states_ {
 	CursorStatePoint,      ///< Normal cursor
 	CursorStateSelect,     ///< Select position
 	CursorStateRectangle,  ///< Rectangle selecting
+	CursorStatePieMenu,    ///< Displaying Pie Menu
 } CursorStates;
 
 /*----------------------------------------------------------------------------
diff --git a/src/include/interface.h b/src/include/interface.h
index 40a841499..cc034eaff 100644
--- a/src/include/interface.h
+++ b/src/include/interface.h
@@ -179,6 +179,7 @@ enum _key_modifiers_ {
 
 	/// pressed mouse button flags
 enum _mouse_buttons_ {
+	NoButton = 0,      ///< No button
 	LeftButton = 2,    ///< Left button on mouse
 	MiddleButton = 4,  ///< Middle button on mouse
 	RightButton = 8,   ///< Right button on mouse
@@ -390,6 +391,10 @@ extern void DrawTimer(void);
 extern void UpdateTimer(void);
 	/// Draw the unit button panel
 extern void DrawButtonPanel(void);
+	/// Update the status line with hints from the button
+extern void UpdateStatusLineForButton(const ButtonAction*);
+	/// Draw the Pie Menu
+extern void DrawPieMenu(void);
 	/// Update the content of the unit button panel
 extern void UpdateButtonPanel(void);
 	/// Handle button click in button panel area
diff --git a/src/include/ui.h b/src/include/ui.h
index 7c8dc76b8..749752847 100644
--- a/src/include/ui.h
+++ b/src/include/ui.h
@@ -249,6 +249,12 @@ typedef struct _ui_ {
 	int           ButtonPanelY;         ///< Button panel screen Y position
 	int           CommandKeyFont;       ///< Command key font
 
+	// Pie Menu
+	GraphicConfig PieMenuBackground;      ///< Optional background image for the piemenu
+	enum _mouse_buttons_ PieMouseButton; ///< Which mouse button pops up the piemenu. Deactivate with the NoButton value.
+	int PieX[8];                         ///< X position of the pies
+	int PieY[8];                         ///< Y position of the pies
+
 	// Map area
 	ViewportMode ViewportMode;          ///< Current viewport mode
 	Viewport*    MouseViewport;         ///< Viewport containing mouse
diff --git a/src/stratagus/mainloop.cpp b/src/stratagus/mainloop.cpp
index 738f2d497..b421772b1 100644
--- a/src/stratagus/mainloop.cpp
+++ b/src/stratagus/mainloop.cpp
@@ -376,6 +376,7 @@ global void UpdateDisplay(void)
 		DrawTimer();
 	}
 
+	DrawPieMenu(); // draw pie menu only if needed
 	DrawMenu(CurrentMenu);
 
 	DrawAnyCursor();
diff --git a/src/ui/botpanel.cpp b/src/ui/botpanel.cpp
index 2a8e3a83d..2f4de3b7e 100644
--- a/src/ui/botpanel.cpp
+++ b/src/ui/botpanel.cpp
@@ -220,7 +220,6 @@ global void DrawButtonPanel(void)
 	int i;
 	int v;
 	Player* player;
-	const UnitStats* stats;
 	const ButtonAction* buttons;
 	char buf[8];
 
@@ -351,29 +350,7 @@ global void DrawButtonPanel(void)
 			//
 			if (ButtonAreaUnderCursor == ButtonAreaButton &&
 					ButtonUnderCursor == i && KeyState != KeyStateInput) {
-				SetStatusLine(buttons[i].Hint);
-				// FIXME: Draw costs
-				v = buttons[i].Value;
-				switch (buttons[i].Action) {
-					case ButtonBuild:
-					case ButtonTrain:
-					case ButtonUpgradeTo:
-						// FIXME: store pointer in button table!
-						stats = &UnitTypes[v]->Stats[player->Player];
-
-						SetCosts(0, UnitTypes[v]->Demand, stats->Costs);
-						break;
-					case ButtonResearch:
-						SetCosts(0, 0, Upgrades[v].Costs);
-						break;
-					case ButtonSpellCast:
-						SetCosts(SpellTypeTable[v]->ManaCost, 0, NULL);
-						break;
-
-					default:
-						ClearCosts();
-						break;
-				}
+				UpdateStatusLineForButton(&buttons[i]);
 			}
 
 			//
@@ -403,6 +380,39 @@ global void DrawButtonPanel(void)
 	}
 }
 
+/**
+**  Update the status line with hints from the button
+**
+**  @param button  Button
+*/
+global void UpdateStatusLineForButton(const ButtonAction* button)
+{
+	int v;
+	const UnitStats* stats;
+
+	SetStatusLine(button->Hint);
+	// FIXME: Draw costs
+	v = button->Value;
+	switch (button->Action) {
+		case ButtonBuild:
+		case ButtonTrain:
+		case ButtonUpgradeTo:
+			// FIXME: store pointer in button table!
+			stats = &UnitTypes[v]->Stats[ThisPlayer->Player];
+			SetCosts(0, UnitTypes[v]->Demand, stats->Costs);
+			break;
+		case ButtonResearch:
+			SetCosts(0, 0, Upgrades[v].Costs);
+			break;
+		case ButtonSpellCast:
+			SetCosts(SpellTypeTable[v]->ManaCost, 0, NULL);
+			break;
+		default:
+			ClearCosts();
+			break;
+	}
+}
+
 /*----------------------------------------------------------------------------
 --  Functions
 ----------------------------------------------------------------------------*/
diff --git a/src/ui/icons.cpp b/src/ui/icons.cpp
index b017d20ca..5e0766df0 100644
--- a/src/ui/icons.cpp
+++ b/src/ui/icons.cpp
@@ -347,7 +347,7 @@ global const char* IdentOfIcon(const Icon* icon)
 global void DrawIcon(const Player* player, Icon* icon, int x, int y)
 {
 	GraphicPlayerPixels(player, icon->Sprite);
-	VideoDraw(icon->Sprite, icon->Index, x, y);
+	VideoDrawClip(icon->Sprite, icon->Index, x, y);
 }
 
 /**
diff --git a/src/ui/mouse.cpp b/src/ui/mouse.cpp
index d6281fec1..f0135a9f0 100644
--- a/src/ui/mouse.cpp
+++ b/src/ui/mouse.cpp
@@ -30,12 +30,16 @@
 
 //@{
 
+#define ICON_SIZE_X (TheUI.ButtonButtons[0].Width)
+#define ICON_SIZE_Y (TheUI.ButtonButtons[0].Height)
+
 /*----------------------------------------------------------------------------
 --  Includes
 ----------------------------------------------------------------------------*/
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <ctype.h>
 
 #include "stratagus.h"
 #include "tileset.h"
@@ -78,6 +82,7 @@ global enum _cursor_on_ CursorOn = CursorOnUnknown;		/// Cursor on field
 /*----------------------------------------------------------------------------
 --  Functions
 ----------------------------------------------------------------------------*/
+local void HandlePieMenuMouseSelection(void);
 
 /**
 **  Cancel building cursor mode.
@@ -657,6 +662,23 @@ global void UIHandleMouseMove(int x, int y)
 		return;
 	}
 
+	if (CursorState == CursorStatePieMenu && CursorOn == CursorOnMap) {
+		// in the map area
+		// make the piemenu "follow" the mouse
+		if (CursorX - CursorStartX > TheUI.PieX[2]) {
+			CursorStartX = CursorX - TheUI.PieX[2];
+		}
+		if (CursorStartX - CursorX > TheUI.PieX[2]) {
+			CursorStartX = CursorX + TheUI.PieX[2];
+		}
+		if (CursorStartY - CursorY > TheUI.PieY[4]) {
+			CursorStartY = CursorY + TheUI.PieY[4];
+		}
+		if (CursorY - CursorStartY > TheUI.PieY[4]) {
+			CursorStartY = CursorY - TheUI.PieY[4];
+		}
+	}
+
 	//
 	//  Move map.
 	//
@@ -1320,6 +1342,7 @@ local void UISelectStateButtonDown(unsigned button __attribute__((unused)))
 	UpdateButtonPanel();
 }
 
+
 /**
 **  Called if mouse button pressed down.
 **
@@ -1374,6 +1397,16 @@ global void UIHandleButtonDown(unsigned button)
 		return;
 	}
 
+	if (CursorState == CursorStatePieMenu) {
+		if (CursorOn == CursorOnMap) {
+			HandlePieMenuMouseSelection();
+		} else {
+			// Pie Menu canceled
+			CursorState = CursorStatePoint;
+		}
+		return;
+	}
+
 	//
 	//  Cursor is on the map area
 	//
@@ -1431,7 +1464,15 @@ global void UIHandleButtonDown(unsigned button)
 			return;
 		}
 
-		if (MouseButtons & LeftButton) { // enter select mode
+		if (MouseButtons & TheUI.PieMouseButton) { // enter pie menu
+			CursorStartX = CursorX;
+			CursorStartY = CursorY;
+			if (NumSelected && Selected[0]->Player == ThisPlayer &&
+					CursorState == CursorStatePoint) {
+				CursorState = CursorStatePieMenu;
+				MustRedraw |= RedrawCursor;
+			}
+		} else if (MouseButtons & LeftButton) { // enter select mode
 			CursorStartX = CursorX;
 			CursorStartY = CursorY;
 			CursorStartScrMapX = CursorStartX - TheUI.MouseViewport->X +
@@ -1458,8 +1499,8 @@ global void UIHandleButtonDown(unsigned button)
 				y = Viewport2MapY(TheUI.MouseViewport, CursorY);
 
 				if (UnitUnderCursor && (unit = UnitOnMapTile(x, y)) &&
-					!UnitUnderCursor->Type->Decoration) {
-					unit->Blink = 4;		// if right click on building -- blink
+						!UnitUnderCursor->Type->Decoration) {
+					unit->Blink = 4;                // if right click on building -- blink
 				} else {		// if not not click on building -- green cross
 					if (ClickMissile) {
 						MakeLocalMissile(MissileTypeByIdent(ClickMissile),
@@ -1525,7 +1566,7 @@ global void UIHandleButtonDown(unsigned button)
 				if (ButtonUnderCursor == 0 && NumSelected == 1) {
 					PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
 					ViewportCenterViewpoint(TheUI.SelectedViewport, Selected[0]->X,
-						Selected[0]->Y, Selected[0]->IX + TileSizeX / 2, 
+						Selected[0]->Y, Selected[0]->IX + TileSizeX / 2,
 						Selected[0]->IY + TileSizeY / 2);
 				}
 			//
@@ -1559,7 +1600,7 @@ global void UIHandleButtonDown(unsigned button)
 			//  clicked on researching button
 			//
 			} else if (ButtonAreaUnderCursor == ButtonAreaResearching) {
-				if (!GameObserve && !GamePaused && 
+				if (!GameObserve && !GamePaused &&
 					PlayersTeamed(ThisPlayer->Player, Selected[0]->Player->Player)) {
 					if (ButtonUnderCursor == 0 && NumSelected == 1) {
 						DebugPrint("Cancel research %s\n" _C_
@@ -1628,6 +1669,18 @@ global void UIHandleButtonUp(unsigned button)
 		return;
 	}
 
+	//
+	//  Pie Menu
+	//
+	if (CursorState == CursorStatePieMenu) {
+		if (CursorStartX == CursorX && CursorStartY == CursorY) {
+			// no move; wait for a click to select the pie
+		} else {
+			// there was a move, handle the selected button/pie
+			HandlePieMenuMouseSelection();
+		}
+	}
+
 	//
 	//  Menu (F10) button
 	//
@@ -1797,7 +1850,7 @@ global void UIHandleButtonUp(unsigned button)
 				if (Selected[0]->Player == ThisPlayer) {
 					char buf[64];
 					if (Selected[0]->Player->UnitTypesCount[Selected[0]->Type->Slot] > 1) {
-						sprintf(buf, "You have ~<%d~> %ss", 
+						sprintf(buf, "You have ~<%d~> %ss",
 							Selected[0]->Player->UnitTypesCount[Selected[0]->Type->Slot],
 							Selected[0]->Type->Name);
 					} else {
@@ -1817,4 +1870,118 @@ global void UIHandleButtonUp(unsigned button)
 	}
 }
 
+/**
+**  Get pie menu under the cursor
+*/
+local int GetPieUnderCursor(void)
+{
+	int i;
+	int x;
+	int y;
+
+	x = CursorX - (CursorStartX - ICON_SIZE_X / 2);
+	y = CursorY - (CursorStartY - ICON_SIZE_Y / 2);
+	for (i = 0; i < 8; ++i) {
+		if (x > TheUI.PieX[i] && x < TheUI.PieX[i] + ICON_SIZE_X &&
+				y > TheUI.PieY[i] && y < TheUI.PieY[i] + ICON_SIZE_Y)	{
+			return i;
+		}
+	}
+	return -1; // no pie under cursor
+}
+
+/**
+**  Draw Pie Menu
+*/
+global void DrawPieMenu(void)
+{
+	int i;
+	const ButtonAction* buttons;
+	Viewport* vp;
+	Player* player;
+	char buf[2] = "?";
+
+	if(CursorState != CursorStatePieMenu)
+		return;
+
+	if (!(buttons = CurrentButtons)) {		// no buttons
+		CursorState = CursorStatePoint;
+		return;
+	}
+
+	vp = TheUI.SelectedViewport;
+	PushClipping();
+	SetClipping(vp->X, vp->Y, vp->EndX, vp->EndY);
+
+	// Draw background
+	if (TheUI.PieMenuBackground.Graphic) {
+		VideoDrawClip(TheUI.PieMenuBackground.Graphic, 0,
+			CursorStartX - TheUI.PieMenuBackground.Graphic->Width / 2,
+			CursorStartY - TheUI.PieMenuBackground.Graphic->Height / 2);
+	}
+	player = Selected[0]->Player;
+
+	for (i = 0; i < TheUI.NumButtonButtons && i < 8; ++i) {
+		if (buttons[i].Pos != -1) {
+			int x;
+			int y;
+
+			x = CursorStartX - ICON_SIZE_X / 2 + TheUI.PieX[i];
+			y = CursorStartY - ICON_SIZE_Y / 2 + TheUI.PieY[i];
+			// Draw icon
+			DrawIcon(player, buttons[i].Icon.Icon, x, y);
+
+			//	Tutorial show command key in icons
+			if (ShowCommandKey) {
+				char* text;
+
+				if (CurrentButtons[i].Key == 27) {
+					text = "ESC";
+				} else {
+					buf[0] = toupper(CurrentButtons[i].Key);
+					text = buf;
+				}
+				VideoDrawText(x + 4, y + 4, GameFont, text);
+			}
+		}
+	}
+
+	PopClipping();
+
+	i = GetPieUnderCursor();
+	if (i != -1 && KeyState != KeyStateInput && buttons[i].Pos!=-1) {
+		UpdateStatusLineForButton(&buttons[i]);
+	}
+}
+
+/**
+**  Handle pie menu mouse selection
+*/
+local void HandlePieMenuMouseSelection(void)
+{
+	int pie;
+
+	if (!CurrentButtons) {  // no buttons
+		return;
+	}
+
+	pie = GetPieUnderCursor();
+	if (pie != -1) {
+		if (CurrentButtons[pie].Action == ButtonButton) {
+			// there is a submenu => stay in piemenu mode
+			// and recenter the piemenu around the cursor
+			CursorStartX = CursorX;
+			CursorStartY = CursorY;
+		} else {
+			CursorState = CursorStatePoint;
+		}
+		DoButtonButtonClicked(pie);
+	} else {
+		CursorState = CursorStatePoint;
+	}
+
+	return;
+}
+
+
 //@}
diff --git a/src/ui/script_ui.cpp b/src/ui/script_ui.cpp
index fbfa84e42..17d91a572 100644
--- a/src/ui/script_ui.cpp
+++ b/src/ui/script_ui.cpp
@@ -1574,6 +1574,57 @@ local int CclDefineUI(lua_State* l)
 					LuaError(l, "Unsupported tag: %s" _C_ value);
 				}
 			}
+		} else if (!strcmp(value, "piemenu")) {
+			k = 0;
+			if (!lua_istable(l, j + 1)) {
+				lua_pushstring(l, "incorrect argument");
+				lua_error(l);
+			}
+			subargs = luaL_getn(l, j + 1);
+			for (k = 0; k < subargs; ++k) {
+				lua_rawgeti(l, j + 1, k + 1);
+				value = LuaToString(l, -1);
+				lua_pop(l, 1);
+				++k;
+				if (!strcmp(value, "file")) {
+					lua_rawgeti(l, j + 1, k + 1);
+					ui->PieMenuBackground.File = strdup(LuaToString(l, -1));
+					lua_pop(l, 1);
+				} else if (!strcmp(value, "radius")) {
+					// Position of the pies in a piemenu
+					int coeffX[] = {    0,  193, 256, 193,   0, -193, -256, -193};
+					int coeffY[] = { -256, -193,   0, 193, 256,  193,    0, -193};
+					int pie;
+					int radius;
+
+					lua_rawgeti(l, j + 1, k + 1);
+					radius = LuaToNumber(l, -1);
+					lua_pop(l, 1);
+					for (pie = 0; pie < 8; ++pie) {
+						ui->PieX[pie]= (coeffX[pie] * radius) >> 8;
+						ui->PieY[pie]= (coeffY[pie] * radius) >> 8;
+					}
+				} else if (!strcmp(value, "mouse-button")) {
+					const char *button;
+
+					lua_rawgeti(l, j + 1, k + 1);
+					button = LuaToString(l, -1);
+					if (!strcmp(button, "right")) {
+						ui->PieMouseButton = RightButton;
+					} else if (!strcmp(button, "middle")) {
+						ui->PieMouseButton = MiddleButton;
+					} else if (!strcmp(button, "left")) {
+						ui->PieMouseButton = LeftButton;
+					} else {
+						ui->PieMouseButton = NoButton;
+					}
+					lua_pop(l, 1);
+				} else {
+					lua_pushfstring(l, "Unsupported tag: %s", value);
+					lua_error(l);
+				}
+			}
+			lua_pop(l, 1);
 		} else if (!strcmp(value, "map-area")) {
 			int w;
 			int h;
diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp
index 6ecb8168a..36c78572c 100644
--- a/src/ui/ui.cpp
+++ b/src/ui/ui.cpp
@@ -99,6 +99,7 @@ local void CleanUIGraphics(UI* ui)
 	VideoSafeFree(ui->MenuPanel.Graphic);
 	VideoSafeFree(ui->MinimapPanel.Graphic);
 	VideoSafeFree(ui->StatusLine.Graphic);
+	VideoSafeFree(ui->PieMenuBackground.Graphic);
 
 	menupanel = ui->MenuPanels;
 	while (menupanel) {
@@ -238,6 +239,10 @@ global void LoadUserInterface(void)
 	if (TheUI.ButtonPanel.File) {
 		TheUI.ButtonPanel.Graphic = LoadGraphic(TheUI.ButtonPanel.File);
 	}
+	if (TheUI.PieMenuBackground.File) {
+		TheUI.PieMenuBackground.Graphic =
+			LoadGraphic(TheUI.PieMenuBackground.File);
+	}
 	if (TheUI.MenuPanel.File) {
 		TheUI.MenuPanel.Graphic = LoadGraphic(TheUI.MenuPanel.File);
 	}
@@ -362,6 +367,9 @@ global void CleanUI(UI* ui)
 	// Menu Button
 	free(ui->MenuPanel.File);
 
+	// Pie Menu
+	free(ui->PieMenuBackground.File);
+
 	// Minimap
 	free(ui->MinimapPanel.File);