Jump to content

  • Log In with Google      Sign In   
  • Create Account


GUI nightmares

  • You cannot reply to this topic
7 replies to this topic

#1 Juliean   GDNet+   -  Reputation: 1928

Like
0Likes
Like

Posted 21 September 2013 - 04:18 AM

Have you ever been told, or told someone not to unroll their own gui framework but to rely on the established available libaries? Well, this pretty much shows why they are right:

TowerStateCtrl::TowerStateCtrl(gui::Widget& parent, const ecs::Entity& tower, const ecs::MessageManager& messages): m_pTower(&tower), m_pMessages(&messages)
{
    gui::Area& area = AddWidget<gui::Area>(0, 0, parent.GetWidth(), parent.GetHeight());
    SetAsMainWidget(&area);
    parent.AddChild(area);

    const int imageX = parent.GetWidth()/12;
    const int imageY = parent.GetHeight()/12;
    const int imageWidth = parent.GetWidth() - imageX*2;
    const int imageHeight = parent.GetHeight() - (int)(imageY*3.75f);
    AddWidget<gui::Image>(imageX, imageY, imageWidth, imageHeight, L"GuiStateback");

    const Tower* pTower = m_pTower->GetComponent<Tower>();

    // name display
    m_pNameLabel = &AddWidget<gui::Label>(0, area.GetHeight() / 16, area.GetWidth(), 32, pTower->m_stName.c_str());

    const int iconSize = area.GetWidth() / 20;
    const int iconOffsetX = area.GetWidth() / 8;

    const int offsetX = iconOffsetX + iconOffsetX;
    const int offsetY = area.GetHeight() / 7;
    const int halfWidth = area.GetWidth() / 2;

    const int labelWidth = halfWidth - offsetX;
    const int labelOffsetX = offsetX + labelWidth;

    // damage display
    AddWidget<gui::Image>(iconOffsetX, offsetY, iconSize, iconSize, L"IcoThunder");
    gui::Label& damageLabel = AddWidget<gui::Label>(offsetX, offsetY, labelWidth, 32, L"Damage");
    damageLabel.SetAlign(gui::LEFT | gui::VCENTER);
    m_pDamageLabel = &AddWidget<gui::Label>(labelOffsetX, offsetY, labelWidth, 32, conv::ToString( pTower->m_atk ).c_str() );
    m_pDamageLabel->SetAlign(gui::RIGHT | gui::VCENTER);

    // speed display
    AddWidget<gui::Image>(iconOffsetX, offsetY*2, iconSize, iconSize, L"IcoHourglass");
    gui::Label& speedLabel = AddWidget<gui::Label>(offsetX, offsetY*2, labelWidth, 32, L"Speed");
    speedLabel.SetAlign(gui::LEFT | gui::VCENTER);
    m_pSpeedLabel = &AddWidget<gui::Label>(labelOffsetX, offsetY*2, labelWidth, 32, conv::ToString( pTower->m_speed ).c_str() );
    m_pSpeedLabel->SetAlign(gui::RIGHT | gui::VCENTER);

    // range display
    AddWidget<gui::Image>(iconOffsetX, offsetY*3, iconSize, iconSize, L"IcoWorld");
    gui::Label& rangeLabel = AddWidget<gui::Label>(offsetX, offsetY*3, labelWidth, 32, L"Range");
    rangeLabel.SetAlign(gui::LEFT | gui::VCENTER);
    m_pRangeLabel = &AddWidget<gui::Label>(labelOffsetX, offsetY*3, labelWidth, 32, conv::ToString( pTower->m_range ).c_str() );
    m_pRangeLabel->SetAlign(gui::RIGHT | gui::VCENTER);

    // level display
    AddWidget<gui::Image>(iconOffsetX, offsetY*4, iconSize, iconSize, L"IcoStar");
    gui::Label& levelLabel = AddWidget<gui::Label>(offsetX, offsetY*4, labelWidth, 32, L"Level");
    levelLabel.SetAlign(gui::LEFT | gui::VCENTER);
    m_pLevelLabel = &AddWidget<gui::Label>(labelOffsetX, offsetY*4, labelWidth, 32, conv::ToString( pTower->m_level + 1 ).c_str() );
    m_pLevelLabel->SetAlign(gui::RIGHT | gui::VCENTER);

	// upgrade button
    const int buttonY = (int)(offsetY * 5.75f);
	m_pUpgradeButton = &AddWidget<gui::Button>(16, buttonY, halfWidth - 32, area.GetHeight() / 6, (L"Upgrade:" + conv::ToString( 50 )).c_str() );
	m_pUpgradeButton->SigReleased.Connect(this, &TowerStateCtrl::OnUpgradeTower);

    const int iconOff = (m_pUpgradeButton->GetHeight() - iconSize) / 2;
    gui::Image& sell = AddWidget<gui::Image>(iconOff, iconOff, iconSize, iconSize, L"IcoUpgrade");
	sell.OnDisable();
    m_pUpgradeButton->AddChild(sell);

    // sell button
    m_pSellButton = &AddWidget<gui::Button>(halfWidth + 16, buttonY, halfWidth - 32, area.GetHeight() / 6, (L"Sell:" + conv::ToString( (int)(pTower->m_cost * 0.75) )).c_str() );
    m_pSellButton->SigReleased.Connect(this, &TowerStateCtrl::OnSellTower);

    gui::Image& money = AddWidget<gui::Image>(iconOff, iconOff, iconSize, iconSize, L"IcoMoney");
	money.OnDisable();
    m_pSellButton->AddChild(money);

    Update();

    OnFocus(true);
}

That monstrousity just to display a small status-widget, without any functionality mind you. I probably could have used more constants (yieks!) or seperated this huge method into smaller ones, but its a mess anyway. Anyone else here who encountered their own "gui horrors"? biggrin.png

Attached Thumbnails

  • gui.png

Edited by Juliean, 21 September 2013 - 04:22 AM.


Sponsor:

#2 unbird   Crossbones+   -  Reputation: 4015

Like
6Likes
Like

Posted 21 September 2013 - 04:59 AM

Meh. Looks pretty slick to me. Try the same with bare Win32 API (or even MFC) and then compare wink.png

Edit: Less tongue-in-cheek, I'm with FLeBlanc here (see below). GUI Control usually come with "option paralysis". If you setup controls manually you can end up with a lot of code. I worked with Delphi once, which had a GUI-designer and a proprietary resource format to store the settings. You rarely touched them with code. C#/Windows.Forms has a designer but generates code - which looks quite like what Juliean posted.

Edited by unbird, 21 September 2013 - 01:46 PM.


#3 FLeBlanc   Crossbones+   -  Reputation: 3061

Like
6Likes
Like

Posted 21 September 2013 - 08:47 AM

GUI code is quite frequently ugly, especially when you go through the steps of hand-configuring the elements as you are doing here. Most GUI frameworks provide some sort of instantiation of elements, whether from XML configuration or some other means, but if you refrain from using these configuration schemes and build your elements by hand instead then your code ends up looking a whole lot like what you have written here. There's just no way to write a fully configurable and flexible GUI system without having to specify a lot of data, either by hand or by config file. So don't feel too bad. :D



#4 Juliean   GDNet+   -  Reputation: 1928

Like
2Likes
Like

Posted 21 September 2013 - 08:56 AM


Try the same with bare Win32 API (or even MFC) and then compare

 

Would I really want to? :D

 

Interesting though, I didn't have any other experience in gui other than looking at some Qt-headers/examples, so I figured this was a real horrid design, specially since one of my fellow students who had done some gui work said that gui programming is supposed to work quite easy oO. Still, one thing that makes this extraordinarly hard to maintain is that I was using a fixed position mode, forcing me to hand-calculate all the sizes, etc...

 


Most GUI frameworks provide some sort of instantiation of elements, whether from XML configuration or some other means, but if you refrain from using these configuration schemes and build your elements by hand instead then your code ends up looking a whole lot like what you have written here.

 

Thats how it ended up for me, too. This, plus a relative metrics mode - 0.0f, 0.0f, 1.0f, 1.0f would mean a widget that fills its whole parent - made things work out much better. Thats the same widget, in XML:

<Controller>
	<Area name="Tower">
		<Main/>
		<Metrics type="rel" x="0.0" y="0.0" w="1.0" h="1.0" />
	</Area>
	<Image name="" img="GuiStateback">
		<Metrics type="rel" x="0.0" y="0.15" w="0.8" h="0.6" />	
		<Center h="1" v="0" />
		<Alignment h="1" v="0" />
		<Image name="" img="IcoThunder">
			<Metrics type="rel" x="0.075" y="0.2" w="0.05" h="1.0" />
			<Center h="0" v="1" />´
			<BorderRel value="1" />
		</Image>
		<Label name="" label="Damage">
			<Metrics type="rel" x="0.3" y="0.2" w="0.4" h="0.15" />
			<Center h="0" v="1" />
			<Align value="4" />
		</Label>
		<Label name="Damagelabel" label="0">
			<Metrics type="rel" x="0.5" y="0.2" w="0.3" h="0.15" />
			<Center h="0" v="1" />
			<Align value="6" />
		</Label>
		<Image name="" img="IcoHourglass">
			<Metrics type="rel" x="0.075" y="0.4" w="0.05" h="1.0" />
			<Center h="0" v="1" />´
			<BorderRel value="1" />
		</Image>
		<Label name="" label="Speed">
			<Metrics type="rel" x="0.3" y="0.4" w="0.4" h="0.15" />
			<Center h="0" v="1" />
			<Align value="4" />
		</Label>
		<Label name="Speedlabel" label="0">
			<Metrics type="rel" x="0.5" y="0.4" w="0.3" h="0.15" />
			<Center h="0" v="1" />
			<Align value="6" />
		</Label>
		<Image name="" img="IcoWorld">
			<Metrics type="rel" x="0.075" y="0.6" w="0.05" h="1.0" />
			<Center h="0" v="1" />´
			<BorderRel value="1" />
		</Image>
		<Label name="" label="Range">
			<Metrics type="rel" x="0.3" y="0.6" w="0.4" h="0.15" />
			<Center h="0" v="1" />
			<Align value="4" />
		</Label>
		<Label name="Rangelabel" label="0">
			<Metrics type="rel" x="0.5" y="0.6" w="0.3" h="0.15" />
			<Center h="0" v="1" />
			<Align value="6" />
		</Label>
		<Image name="" img="IcoStar">
			<Metrics type="rel" x="0.075" y="0.8" w="0.05" h="1.0" />
			<Center h="0" v="1" />´
			<BorderRel value="1" />
		</Image>
		<Label name="" label="Level">
			<Metrics type="rel" x="0.3" y="0.8" w="0.4" h="0.15" />
			<Center h="0" v="1" />
			<Align value="4" />
		</Label>
		<Label name="Levellabel" label="0">
			<Metrics type="rel" x="0.5" y="0.8" w="0.3" h="0.15" />
			<Center h="0" v="1" />
			<Align value="6" />
		</Label>
	</Image>
	<Label name="Namelabel" label="">
		<Align value="1" />
		<Metrics type="rel" x="0.0" y="0.05" w="1.0" h="0.2" />
	</Label>
	<Button name="Upgradebutton" label="Upgrade: 0">
		<Metrics type="rel" x="0.275" y="0.775" w="0.4" h="0.175" />	
		<Center h="1" v="0" />
		<Image name="" img="IcoUpgrade">
			<Disabled/>
			<Metrics type="rel" x="0.075" y="0.5" w="1.0" h="0.5" />
			<Center h="0" v="1" />´
			<BorderRel value="2" />
		</Image>
	</Button> 
	<Button name="Sellbutton" label="Sell: 0">
		<Metrics type="rel" x="0.725" y="0.775" w="0.4" h="0.175" />	
		<Center h="1" v="0" />
		<Image name="" img="IcoMoney">
			<Disabled/>
			<Metrics type="rel" x="0.075" y="0.5" w="1.0" h="0.5" />
			<Center h="0" v="1" />´
			<BorderRel value="2" />
		</Image>
	</Button>
</Controller>

Still a lot of code, but at least there is some consistency ;)



#5 boogyman19946   Members   -  Reputation: 963

Like
0Likes
Like

Posted 11 January 2014 - 02:00 PM

Here's the code that I generally write to assemble UI's. This one in particular is in Java and assembles a UI for the lobby of an online card game (screenshot below). As you can see, I take some of the more interesting UI components into their own classes so that I don't have to worry about their function at the same time that I'm worrying about the spatial relationships of the components. 

package residentevil.game.ui.lobby;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.HashMap;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import library.menu.MenuScreen;
import library.menu.MenuStack;
import residentevil.cards.Card;
import residentevil.cards.Character;
import residentevil.game.ui.CardButton;
import residentevil.game.ui.CardPreviewControl;
import residentevil.game.ui.CardPreviewPanel;

public class LobbyScreen extends MenuScreen implements ActionListener {
   private final static String READY_ACTION = "Ready";
   private final static String CHARACTER_SELECT_ACTION = "Character Selection";
   private final static String QUIT_ACTION = "Quit";

   private final static int CARD_PREVIEW_PANEL_WIDTH = 250;
   private final static Dimension MAX_BUTTON_SIZE = new Dimension(126, 56);
   private final static int STRUT_SIZE = 15;
   
   private final static float PLAYER_LIST_SIZE_PERCENTAGE = 0.32f;
   private final static float CHARACTER_BUTTON_PANEL_SIZE_PERCENTAGE = 0.45f;
   
   private MenuStack mMenuStack;
   
   private CardPreviewPanel mCardPreview;
   private CardPreviewControl mCardPreviewControl;
   
   private HashMap<String, PlayerStatus> mPlayers;
   private PlayerTableModel mPlayerList;
   private JScrollPane mPlayerTablePanel;
   
   private CardSelectionPanel mCharacterCardPanel;
   private StatusListener mStatusListener;
   
   public LobbyScreen(MenuStack menuStack) {
      super();
      
      mMenuStack = menuStack;
      mPlayers = new HashMap<>();
      
      createWidgets();
      addComponentListener();
      
      mStatusListener = new StatusListener() {
         public void toggleReady() { }
         public void changeCharacter(String characterName) { }
         public void quit() {  }
      };
   }
   
   private void addComponentListener() {
      addComponentListener(new ComponentListener() {
         @Override
         public void componentResized(ComponentEvent e) {
            resizePlayerTablePanel();
            mCharacterCardPanel.resizeCardSelectionPanel(CHARACTER_BUTTON_PANEL_SIZE_PERCENTAGE);
            revalidate();
            repaint();
         }
         @Override
         public void componentHidden(ComponentEvent e) {}
         @Override
         public void componentMoved(ComponentEvent e) {}
         @Override
         public void componentShown(ComponentEvent e) {}
      });
   }
   
   /*************************************************************************************
    *                                    UI ASSEMBLY                                    *
    *************************************************************************************/
   
   private void createWidgets() {
      setLayout(new BorderLayout());
      
      createPreviewPanel();
      createMainPanel();
   }
   
   private void createPreviewPanel() {
      mCardPreview = new CardPreviewPanel();
      mCardPreview.setPreferredSize(new Dimension(CARD_PREVIEW_PANEL_WIDTH, Integer.MAX_VALUE));
      mCardPreviewControl = new CardPreviewControl(mCardPreview);
      add(mCardPreview, BorderLayout.WEST);
   }
   
   private void createMainPanel() {
      JPanel mainPanel = new JPanel();
      mainPanel.setOpaque(false);
      mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
      
      createConnectedPlayersTable(mainPanel);
      mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
      createCharacterSelectionPanel(mainPanel);
      mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
      createControlButtons(mainPanel);
      mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
      
      add(mainPanel, BorderLayout.CENTER);
   }
   
   private void createConnectedPlayersTable(JPanel mainPanel) {
      mPlayerList = new PlayerTableModel();
      JTable playerTable = new JTable(mPlayerList);
      playerTable.setAlignmentX(Component.CENTER_ALIGNMENT);
      
      mPlayerTablePanel = new JScrollPane(playerTable);
      resizePlayerTablePanel();
      
      mainPanel.add(mPlayerTablePanel);
   }
   
   private void resizePlayerTablePanel() {
      Dimension parentSize = getSize();
      Dimension preferredSize = new Dimension(parentSize);
      preferredSize.height = (int)(parentSize.height*PLAYER_LIST_SIZE_PERCENTAGE);
      mPlayerTablePanel.setPreferredSize(preferredSize);
   }
   
   private void createCharacterSelectionPanel(JPanel mainPanel) {
      JLabel charactersLabel = new JLabel("Characters");
      charactersLabel.setForeground(Color.WHITE);
      charactersLabel.setAlignmentX(CENTER_ALIGNMENT);
      
      mCharacterCardPanel = new CardSelectionPanel(mCardPreviewControl, this, CHARACTER_SELECT_ACTION);
      mCharacterCardPanel.resizeCardSelectionPanel(CHARACTER_BUTTON_PANEL_SIZE_PERCENTAGE);
      
      mainPanel.add(charactersLabel);
      mainPanel.add(mCharacterCardPanel);
   }
   
   private void createControlButtons(JPanel mainPanel) {
      JPanel buttonPanel = new JPanel();
      buttonPanel.setOpaque(false);
      buttonPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
      
      JButton startButton = createButton("Ready", READY_ACTION);
      JButton quitButton = createButton("Quit", QUIT_ACTION);
      
      buttonPanel.add(startButton);
      buttonPanel.add(quitButton);
      
      mainPanel.add(buttonPanel);
   }
   
   private JButton createButton(String title, String action) {
      JButton button = new JButton(title);
      button.setActionCommand(action);
      button.addActionListener(this);
      button.setMaximumSize(MAX_BUTTON_SIZE);
      return button;
   }
   
   /***************************************************************************
    *                                INTERFACE                                *
    ***************************************************************************/
   
   public void setStatusListener(StatusListener statusListener) {
      mStatusListener = statusListener;
   }
   
   public void addPlayer(String playerName) {
      PlayerStatus status = new PlayerStatus();
      status.mName = playerName;
      status.mCharacter = "Unknown";
      status.mReady = false;
      
      mPlayers.put(playerName, status);
      mPlayerList.addPlayer(status);
   }
   
   public void updatePlayerStatus(String playerName, String characterName, boolean ready) {
      PlayerStatus status = mPlayers.get(playerName);
      status.mCharacter = characterName;
      status.mReady = ready;
      mPlayerList.fireTableDataChanged();
   }
   
   public void removePlayer(String playerName) {
      PlayerStatus status = mPlayers.get(playerName);
      mPlayers.remove(playerName);
      mPlayerList.removePlayer(status);
   }
   
   public void addCharacterCards(Character cards[]) {
      mCharacterCardPanel.removeAll();
      mCharacterCardPanel.createCardButtons(cards);
      revalidate();
      repaint();
   }

   @Override
   public void actionPerformed(ActionEvent actionEvent) {
      String command = actionEvent.getActionCommand();
      
      if(command.equals(READY_ACTION)) {
         mStatusListener.toggleReady();
      } else if(command.equals(CHARACTER_SELECT_ACTION)) {
         CardButton cardButton = (CardButton)actionEvent.getSource();
         Card card = cardButton.getCard();
         mStatusListener.changeCharacter(card.getName());
      } else if(command.equals(QUIT_ACTION)) {
         mStatusListener.quit();
         mMenuStack.popScreen();
      }
   }
}

Unfortunately, this is probably one of the more tame UI code that I have XD 

 

Here is a screenshot of the lobby:
RELobbyUI.png

 

The pink squares are where that card images are. I blocked them out because they are not my work and I don't want to violate any rules :) Basically, when the player hovers above the cards, information about the card is updated in the panels on the left.


"If highly skilled generalists are rare, though, then highly skilled innovators are priceless." - ApochPiQ

My personal links :)
- Khan Academy - For all your math needs
- Java API Documentation - For all your Java info needs :D
- C++ Standard Library Reference - For some of your C++ needs ^.^

#6 Nübček Pænus   Members   -  Reputation: 147

Like
0Likes
Like

Posted 15 January 2014 - 04:55 PM

I'm looking forward to writing my own GUI framework for my current project. I've written one before but it used SDL surfaces and real-time transformations (SDL_gfx), so it was very slow and painful to think about. I've written buttons so far in my current one (OpenGL based) and they look nice, but I'm still pondering how to do the other controls like text input and listviews.
 

Originally I planned on emulating DOS/ncurses style GUIs since that would be much easier to do and I already have an output system for that, but it wouldn't fit with the cartoony look of the rest of the application which is a shame.


Edited by Nübček Pænus, 15 January 2014 - 04:56 PM.


#7 swiftcoder   Senior Moderators   -  Reputation: 8144

Like
1Likes
Like

Posted 15 January 2014 - 08:11 PM

This reminds me of the reasons why I wrote simplui, once upon a time: roughly 90% of the time, what one actually needs is an immediate-mode GUI with auto-layout.

 

Of course, the corollary to that rule is that the other 10% of the time you need the whole of QT, and anything less is miserable.


Tristam MacDonald - SDE II @ Amazon - swiftcoding        [Need to sync your files via the cloud? | Need affordable web hosting?]


#8 Joshhua5   Members   -  Reputation: 291

Like
0Likes
Like

Posted 12 April 2014 - 05:23 AM

I'm about to take on the adventure of writing the GUI's for my game. I hope it looks better although I promise nothing!







PARTNERS