Skip to content
This repository has been archived by the owner on Mar 6, 2018. It is now read-only.

Commit

Permalink
Improvements to user interface classes (#183)
Browse files Browse the repository at this point in the history
* Improvements to user interface classes

- Add `MouseScrollEvent` which fires when the mouse wheel is scrolled
- Improve scrollable containers so they don't show scrollbars when not necessary
- Add `ProtogameEventArgs` which passes `IGameContext` to click handlers
- Add `Icon` property to `ListItem`
- Add padding methods to `ISkinLayout` to customize padding of containers

* Modify scrollable container to only use render targets as large as needed
  • Loading branch information
hach-que committed May 29, 2017
1 parent 9f16839 commit 203c33c
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 40 deletions.
2 changes: 2 additions & 0 deletions Build/Projects/Protogame.definition
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@
<Compile Include="Events\MouseMoveEvent.cs" />
<Compile Include="Events\MousePressEvent.cs" />
<Compile Include="Events\MouseReleaseEvent.cs" />
<Compile Include="Events\MouseScrollEvent.cs" />
<Compile Include="Events\ProtogameEventsIoCModule.cs" />
<Compile Include="Events\ProtogameEventsModule.cs" />
<Compile Include="Events\StaticEventBinder.cs" />
Expand Down Expand Up @@ -835,6 +836,7 @@
<Compile Include="UserInterface\Control\ListView.cs" />
<Compile Include="UserInterface\Control\MainMenu.cs" />
<Compile Include="UserInterface\Control\MenuItem.cs" />
<Compile Include="UserInterface\Control\ProtogameEventArgs.cs" />
<Compile Include="UserInterface\Control\TextBox.cs" />
<Compile Include="UserInterface\Control\TextureViewer.cs" />
<Compile Include="UserInterface\Control\TreeItem.cs" />
Expand Down
10 changes: 10 additions & 0 deletions Protogame/Events/EventEngineHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,16 @@ private void UpdateMouse(IGameContext gameContext)
});
}

if (_lastMouseState.HasValue)
{
if (mouseState.ScrollWheelValue != _lastMouseState.Value.ScrollWheelValue)
{
_eventEngine.Fire(
gameContext,
new MouseScrollEvent { ScrollDelta = (_lastMouseState.Value.ScrollWheelValue - mouseState.ScrollWheelValue), MouseState = mouseState });
}
}

_lastMouseState = mouseState;
}

Expand Down
13 changes: 13 additions & 0 deletions Protogame/Events/MouseScrollEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Protogame
{
/// <summary>
/// Fired when the mouse wheel is scrolled.
/// </summary>
public class MouseScrollEvent : MouseEvent
{
/// <summary>
/// The amount the mouse wheel has scrolled by.
/// </summary>
public float ScrollDelta { get; set; }
}
}
4 changes: 2 additions & 2 deletions Protogame/UserInterface/Control/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public Button()
State = ButtonUIState.None;
}

public event EventHandler Click;
public event EventHandler<ProtogameEventArgs> Click;

public IContainer[] Children => IContainerConstant.EmptyContainers;

Expand Down Expand Up @@ -111,7 +111,7 @@ public bool HandleEvent(ISkinLayout skin, Rectangle layout, IGameContext context
{
if (Click != null && State == ButtonUIState.Clicked)
{
Click(this, new EventArgs());
Click(this, new ProtogameEventArgs(context));
}

_shouldAppearPressedWhenMouseIsOver = false;
Expand Down
157 changes: 127 additions & 30 deletions Protogame/UserInterface/Control/Container/ScrollableContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,42 +39,88 @@ public class ScrollableContainer : IContainer

public RenderTarget2D ChildContent => _renderTarget;

public bool NeedsVerticalScrollbar { get; private set; }

public bool NeedsHorizontalScrollbar { get; private set; }

public int ChildHeight { get; private set; }

public int ChildWidth { get; private set; }

public virtual void Render(IRenderContext context, ISkinLayout skinLayout, ISkinDelegator skinDelegator, Rectangle layout)
{
var layoutWidth = layout.Width - skinLayout.HorizontalScrollBarHeight;
var layoutHeight = layout.Height - skinLayout.VerticalScrollBarWidth;
var constrainedLayoutWidth = layout.Width - skinLayout.VerticalScrollBarWidth;
var constrainedLayoutHeight = layout.Height - skinLayout.HorizontalScrollBarHeight;

var realLayoutWidth = layout.Width;
var realLayoutHeight = layout.Height;

NeedsVerticalScrollbar = false;
NeedsHorizontalScrollbar = false;

int childWidth, childHeight;
if (!(_child is IHasDesiredSize))
{
childWidth = layoutWidth;
childHeight = layoutHeight;
childWidth = layout.Width;
childHeight = layout.Height;
}
else
{
var hasDesiredSize = (IHasDesiredSize) _child;
childWidth = hasDesiredSize.GetDesiredWidth(skinLayout) ?? layoutWidth;
childHeight = hasDesiredSize.GetDesiredHeight(skinLayout) ?? layoutHeight;
if (childWidth < layoutWidth)
childWidth = hasDesiredSize.GetDesiredWidth(skinLayout) ?? layout.Width;
childHeight = hasDesiredSize.GetDesiredHeight(skinLayout) ?? layout.Height;
if (childHeight > layout.Height)
{
NeedsVerticalScrollbar = true;
realLayoutWidth = constrainedLayoutWidth;

// Introducing a vertical scrollbar modifies the width, so update that.
childWidth = hasDesiredSize.GetDesiredWidth(skinLayout) ?? constrainedLayoutWidth;

if (childWidth > constrainedLayoutWidth)
{
NeedsHorizontalScrollbar = true;
realLayoutHeight = constrainedLayoutHeight;
}
}
else if (childWidth > layout.Width)
{
childWidth = layoutWidth;
NeedsHorizontalScrollbar = true;
realLayoutHeight = constrainedLayoutHeight;

// Introducing a horizontal scrollbar modifies the height, so update that.
childHeight = hasDesiredSize.GetDesiredHeight(skinLayout) ?? constrainedLayoutHeight;

if (childHeight > constrainedLayoutHeight)
{
NeedsVerticalScrollbar = true;
realLayoutWidth = constrainedLayoutWidth;
}
}

if (childWidth < realLayoutWidth)
{
childWidth = realLayoutWidth;
}
if (childHeight < layoutHeight)
if (childHeight < realLayoutHeight)
{
childHeight = layoutHeight;
childHeight = realLayoutHeight;
}
}

if (_renderTarget == null || _renderTarget.Width != childWidth ||
_renderTarget.Height != childHeight)
if (_renderTarget == null || _renderTarget.Width != realLayoutWidth ||
_renderTarget.Height != realLayoutHeight)
{
_renderTarget?.Dispose();

_renderTarget = new RenderTarget2D(
context.GraphicsDevice,
Math.Max(1, childWidth),
Math.Max(1, childHeight));
Math.Max(1, realLayoutWidth),
Math.Max(1, realLayoutHeight));
}

ChildWidth = childWidth;
ChildHeight = childHeight;

context.SpriteBatch.End();
context.PushRenderTarget(_renderTarget);
Expand All @@ -87,7 +133,11 @@ public virtual void Render(IRenderContext context, ISkinLayout skinLayout, ISkin
context,
skinLayout,
skinDelegator,
new Rectangle(0, 0, childWidth, childHeight));
new Rectangle(
-(int)(ScrollX * (System.Math.Max(childWidth, realLayoutWidth) - realLayoutWidth)),
-(int)(ScrollY * (System.Math.Max(childHeight, realLayoutHeight) - realLayoutHeight)),
childWidth,
childHeight));
}
finally
{
Expand Down Expand Up @@ -120,15 +170,58 @@ public virtual void Update(ISkinLayout skinLayout, Rectangle layout, GameTime ga
_child?.Update(skinLayout, layout, gameTime, ref stealFocus);
}

private bool AnyChildFocused(IContainer container)
{
foreach (var child in container.Children)
{
if (child?.Focused ?? false)
{
return true;
}

if (child != null)
{
if (!(child is ScrollableContainer))
{
if (AnyChildFocused(child))
{
return true;
}
}
}
}

return false;
}

public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext context, Event @event)
{
if (_renderTarget == null)
{
return false;
}

var layoutWidth = layout.Width - skinLayout.HorizontalScrollBarHeight;
var layoutHeight = layout.Height - skinLayout.VerticalScrollBarWidth;
var layoutWidth = layout.Width - (NeedsVerticalScrollbar ? skinLayout.VerticalScrollBarWidth : 0);
var layoutHeight = layout.Height - (NeedsHorizontalScrollbar ? skinLayout.HorizontalScrollBarHeight : 0);

var mouseScrollEvent = @event as MouseScrollEvent;

if (mouseScrollEvent != null)
{
if (this.Focused || AnyChildFocused(this))
{
var scrollAmount = (1f / ChildHeight) * 40f;
ScrollY += (mouseScrollEvent.ScrollDelta / 120f) * scrollAmount;
if (ScrollY < 0)
{
ScrollY = 0;
}
if (ScrollY > 1)
{
ScrollY = 1;
}
}
}

if (_isVerticalScrolling)
{
Expand All @@ -141,9 +234,9 @@ public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext c
newVerticalScrollbarPosition = MathHelper.Clamp(
newVerticalScrollbarPosition,
0,
layoutHeight - (int)(layoutHeight / (float)_renderTarget.Height * layoutHeight));
layoutHeight - (int)(layoutHeight / (float)ChildHeight * layoutHeight));
ScrollY = newVerticalScrollbarPosition /
(layoutHeight - layoutHeight / (float)_renderTarget.Height * layoutHeight);
(layoutHeight - layoutHeight / (float)ChildHeight * layoutHeight);
return true;
}

Expand All @@ -163,9 +256,9 @@ public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext c
newHorizontalScrollbarPosition = MathHelper.Clamp(
newHorizontalScrollbarPosition,
0,
layoutWidth - (int)(layoutWidth / (float)_renderTarget.Width * layoutWidth));
layoutWidth - (int)(layoutWidth / (float)ChildWidth * layoutWidth));
ScrollX = newHorizontalScrollbarPosition /
(layoutWidth - layoutWidth / (float)_renderTarget.Width * layoutWidth);
(layoutWidth - layoutWidth / (float)ChildWidth * layoutWidth);
return true;
}

Expand All @@ -179,21 +272,21 @@ public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext c
var mousePressEvent = @event as MousePressEvent;

var horizontalScrollBarRectangle = new Rectangle(
(int)(layout.X + ScrollX * (layoutWidth - layoutWidth / (float)_renderTarget.Width * layoutWidth)),
(int)(layout.X + ScrollX * (layoutWidth - layoutWidth / (float)ChildWidth * layoutWidth)),
layout.Y + layout.Height - skinLayout.HorizontalScrollBarHeight,
(int)(layoutWidth / (float)_renderTarget.Width * layoutWidth),
Math.Max((int)(layoutWidth / (float)ChildWidth * layoutWidth), 16),
skinLayout.HorizontalScrollBarHeight);
var verticalScrollBarRectangle = new Rectangle(
layout.X + layout.Width - skinLayout.VerticalScrollBarWidth,
(int)(layout.Y + ScrollY * (layoutHeight - layoutHeight / (float)_renderTarget.Height * layoutHeight)),
(int)(layout.Y + ScrollY * (layoutHeight - layoutHeight / (float)ChildHeight * layoutHeight)),
skinLayout.VerticalScrollBarWidth,
(int)(layoutHeight / (float)_renderTarget.Height * layoutHeight));
Math.Max((int)(layoutHeight / (float)ChildHeight * layoutHeight), 16));

if (mousePressEvent != null && mousePressEvent.Button == MouseButton.Left)
{
if (horizontalScrollBarRectangle.Contains(mousePressEvent.MouseState.Position))
{
if (_renderTarget.Width > layout.Width)
if (ChildWidth > layout.Width)
{
_isHorizontalScrolling = true;
_horizontalScrollOffset = mousePressEvent.MouseState.Position.X - horizontalScrollBarRectangle.X;
Expand All @@ -205,7 +298,7 @@ public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext c

if (verticalScrollBarRectangle.Contains(mousePressEvent.MouseState.Position))
{
if (_renderTarget.Height > layout.Height)
if (ChildHeight > layout.Height)
{
_isVerticalScrolling = true;
_verticalScrollOffset = mousePressEvent.MouseState.Position.Y - verticalScrollBarRectangle.Y;
Expand All @@ -227,8 +320,8 @@ public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext c
int scrollXPixels = 0, scrollYPixels = 0;
if (mouseEvent != null)
{
scrollXPixels = (int)(ScrollX * (Math.Max(_renderTarget.Width, layoutWidth) - layoutWidth));
scrollYPixels = (int)(ScrollY * (Math.Max(_renderTarget.Height, layoutHeight) - layoutHeight));
scrollXPixels = (int)(ScrollX * (Math.Max(ChildWidth, layoutWidth) - layoutWidth));
scrollYPixels = (int)(ScrollY * (Math.Max(ChildHeight, layoutHeight) - layoutHeight));

originalState = mouseEvent.MouseState;
mouseEvent.MouseState = new MouseState(
Expand All @@ -252,7 +345,11 @@ public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext c
}
}

_child.HandleEvent(skinLayout, layout, context, @event);
_child.HandleEvent(skinLayout, new Rectangle(
layout.X,
layout.Y,
layout.Width - (NeedsVerticalScrollbar ? skinLayout.VerticalScrollBarWidth : 0),
layout.Height - (NeedsHorizontalScrollbar ? skinLayout.HorizontalScrollBarHeight : 0)), context, @event);

// Restore event state.
if (mouseEvent != null)
Expand Down
22 changes: 18 additions & 4 deletions Protogame/UserInterface/Control/Container/SingleContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class SingleContainer : IContainer, IHasDesiredSize

public IContainer[] Children => new[] { _child };

public bool Focused { get; set; }
public virtual bool Focused { get; set; }

public int Order { get; set; }

Expand All @@ -34,7 +34,7 @@ public class SingleContainer : IContainer, IHasDesiredSize
public virtual void Render(IRenderContext context, ISkinLayout skinLayout, ISkinDelegator skinDelegator, Rectangle layout)
{
skinDelegator.Render(context, layout, this);
_child?.Render(context, skinLayout, skinDelegator, layout);
_child?.Render(context, skinLayout, skinDelegator, GetChildLayout(layout, skinLayout));
}

public void SetChild(IContainer child)
Expand All @@ -55,12 +55,26 @@ public void SetChild(IContainer child)

public virtual void Update(ISkinLayout skinLayout, Rectangle layout, GameTime gameTime, ref bool stealFocus)
{
_child?.Update(skinLayout, layout, gameTime, ref stealFocus);
_child?.Update(skinLayout, GetChildLayout(layout, skinLayout), gameTime, ref stealFocus);
}

public bool HandleEvent(ISkinLayout skinLayout, Rectangle layout, IGameContext context, Event @event)
{
return _child != null && _child.HandleEvent(skinLayout, layout, context, @event);
return _child != null && _child.HandleEvent(skinLayout, GetChildLayout(layout, skinLayout), context, @event);
}

protected Rectangle GetChildLayout(Rectangle layout, ISkinLayout skinLayout)
{
var leftPadding = skinLayout.GetLeftPadding(this, _child);
var rightPadding = skinLayout.GetRightPadding(this, _child);
var topPadding = skinLayout.GetTopPadding(this, _child);
var bottomPadding = skinLayout.GetBottomPadding(this, _child);

return new Rectangle(
layout.X + leftPadding,
layout.Y + topPadding,
layout.Width - leftPadding - rightPadding,
layout.Height - topPadding - bottomPadding);
}
}
}

0 comments on commit 203c33c

Please sign in to comment.