Skip to content

Instantly share code, notes, and snippets.

@occar421
Last active July 25, 2020 00:32
Show Gist options
  • Save occar421/e6599441f77cf4a13975 to your computer and use it in GitHub Desktop.
Save occar421/e6599441f77cf4a13975 to your computer and use it in GitHub Desktop.
Implemantation of button for OpenTK GameWIndow with ReactiveExtensions.

About this Gist

##Description This Gist shows an implementation the simple button on OpenTK GameWindow with ReactiveExtensions. Code is divided into base class "UIWindow" and sub class "MyWindow".

UIWindow provides things for implemeting the base of UI. In more details, it has collection of UI element, make UI elements draw, and fires click events. Because it hides SwapBuffers method and provides new SwapBuffer, who making sub class hasn't to pay attention to difference between UIWindow and GameWindow.

Files

  • UIElement.cs
    UIElement class provides base of UI element. ex. position, color, hit test, event. Button class overrides UIElement.Draw method, only drawing.

  • UIWindow.cs
    UIWindow class is same GameWindow class for who making sub class. GameWidow.SwapBuffers method is explicitly hidden and we may use UIWindow.SwapBuffers method automatically. In UIWindow.SwapBuffers, UIElements that added as control are drawn into window.

  • Layout.cs, Main.cs
    They are code of partial class "MyWindow" extends UIWindow, for show how to use. I tried to make this like WindowsForms. As you can see, UI elements are registered in Layout.cs, Initialized in InitializeComponent method, and attached OnClick event in MyWindow ctor. Main.cs is same as GameWindow except attaching event.

If you like this implementation, please star this. Many stars might make me make repository of this GUI(not only button, not simple).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using OpenTK.Graphics;
using OpenTK_RxUI;
namespace OpenTK_RxUI_Test
{
partial class MyWindow : UIWindow
{
//UI Elements
Button WhiteButton;
Button RedButton;
Button GreenButton;
void InitializeComponent()
{
WhiteButton = new Button(new Rectangle(50, 50, 100, 100), Color4.WhiteSmoke);
UIElements.AddFirst(WhiteButton);
RedButton = new Button(new Rectangle(100, 100, 100, 100), Color4.Coral);
UIElements.AddFirst(RedButton);
GreenButton = new Button(new Rectangle(75, 75, 100, 100), Color4.ForestGreen);
UIElements.AddFirst(GreenButton);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using OpenTK.Graphics.OpenGL;
using OpenTK_RxUI;
namespace OpenTK_RxUI_Test
{
static public class OpenTKRxUIStarter
{
static public void Do()
{
using (MyWindow window = new MyWindow(800, 600))
{
window.Run(10.0);
}
}
}
partial class MyWindow : UIWindow
{
readonly Vector4 lightPosition = new Vector4(200.0f, 150f, 500.0f, 0.0f);
readonly Color4 lightAmbient = new Color4(0.2f, 0.2f, 0.2f, 1.0f);
readonly Color4 lightDiffuse = new Color4(0.7f, 0.7f, 0.7f, 1.0f);
readonly Color4 lightSpecular = new Color4(1.0f, 1.0f, 1.0f, 1.0f);
readonly Color4 materialAmbient = new Color4(0.24725f, 0.1995f, 0.0225f, 1.0f);
readonly Color4 materialDiffuse = new Color4(0.75164f, 0.60648f, 0.22648f, 1.0f);
readonly Color4 materialSpecular = new Color4(0.628281f, 0.555802f, 0.366065f, 1.0f);
readonly float materialShininess = 51.4f;
Matrix4 rotate = Matrix4.Identity;
float zoom = 1.0f;
bool isCube = false;
public MyWindow(int width, int height)
: base(width, height)
{
InitializeComponent();
WhiteButton.OnClick += (sender, e) =>
{
isCube = !isCube;
Console.WriteLine("White");
};
RedButton.OnClick += (sender, e) =>
{
Console.WriteLine("Red");
};
GreenButton.OnClick += (sender, e) =>
{
Console.WriteLine("Green");
};
//Tracking
MouseMoveSource.SkipUntil(MouseDownSource.Where(e => e.Button == MouseButton.Right))
.TakeUntil(MouseUpSource.Where(e => e.Button == MouseButton.Right))
.Repeat()
.Subscribe(e =>
{
Vector2 delta = new Vector2(e.XDelta, e.YDelta);
delta /= (float)Math.Sqrt(this.Width * this.Width + this.Height * this.Height);
float length = delta.Length;
if (length > 0.0)
{
float rad = length * MathHelper.Pi;
float theta = (float)Math.Sin(rad) / length;
Quaternion after = new Quaternion(delta.Y * theta, delta.X * theta, 0.0f, (float)Math.Cos(rad));
rotate = rotate * Matrix4.Rotate(after);
}
});
//Zoom
MouseWheelChangedSource.Subscribe(e =>
{
zoom *= (float)Math.Pow(1.2, e.DeltaPrecise);
if (zoom > 2.0f)
{
zoom = 2.0f;
}
if (zoom < 0.5f)
{
zoom = 0.5f;
}
});
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
GL.ClearColor(Color4.Black);
GL.Enable(EnableCap.DepthTest);
//裏面削除、反時計回りが表でカリング
GL.Enable(EnableCap.CullFace);
GL.CullFace(CullFaceMode.Back);
GL.FrontFace(FrontFaceDirection.Ccw);
//ライティングON Light0を有効化
GL.Enable(EnableCap.Lighting);
GL.Enable(EnableCap.Light0);
//法線の正規化
GL.Enable(EnableCap.Normalize);
GL.Light(LightName.Light0, LightParameter.Position, lightPosition);
GL.Light(LightName.Light0, LightParameter.Ambient, lightAmbient);
GL.Light(LightName.Light0, LightParameter.Diffuse, lightDiffuse);
GL.Light(LightName.Light0, LightParameter.Specular, lightSpecular);
GL.Material(MaterialFace.Front, MaterialParameter.Ambient, materialAmbient);
GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, materialDiffuse);
GL.Material(MaterialFace.Front, MaterialParameter.Specular, materialSpecular);
GL.Material(MaterialFace.Front, MaterialParameter.Shininess, materialShininess);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
GL.Viewport(ClientRectangle);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
#region TransFormationMatrix
Matrix4 modelView = Matrix4.LookAt(Vector3.UnitZ * 10 / zoom, Vector3.Zero, Vector3.UnitY);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadMatrix(ref modelView);
GL.MultMatrix(ref rotate);
Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4 / zoom, (float)this.Width / (float)this.Height, 1.0f, 64.0f);
GL.MatrixMode(MatrixMode.Projection);
GL.LoadMatrix(ref projection);
#endregion
GL.MatrixMode(MatrixMode.Modelview);
GL.PushMatrix();
GL.Translate(2 * Vector3.UnitX);
if (isCube)
DrawCube();
GL.PopMatrix();
GL.PushMatrix();
GL.Translate(-2 * Vector3.UnitX);
if (!isCube)
DrawSphere();
GL.PopMatrix();
SwapBuffers();
}
//立方体を描画する
private void DrawCube()
{
GL.Begin(BeginMode.Quads);
GL.Normal3(1.0f, 0.0f, 0.0f);
GL.Vertex3(1.0f, 1.0f, 1.0f);
GL.Vertex3(1.0f, -1.0f, 1.0f);
GL.Vertex3(1.0f, -1.0f, -1.0f);
GL.Vertex3(1.0f, 1.0f, -1.0f);
GL.Normal3(-1.0f, 0.0f, 0.0f);
GL.Vertex3(-1.0f, 1.0f, 1.0f);
GL.Vertex3(-1.0f, 1.0f, -1.0f);
GL.Vertex3(-1.0f, -1.0f, -1.0f);
GL.Vertex3(-1.0f, -1.0f, 1.0f);
GL.Normal3(0.0f, 1.0f, 0.0f);
GL.Vertex3(1.0f, 1.0f, 1.0f);
GL.Vertex3(1.0f, 1.0f, -1.0f);
GL.Vertex3(-1.0f, 1.0f, -1.0f);
GL.Vertex3(-1.0f, 1.0f, 1.0f);
GL.Normal3(0.0f, -1.0f, 0.0f);
GL.Vertex3(1.0f, -1.0f, 1.0f);
GL.Vertex3(-1.0f, -1.0f, 1.0f);
GL.Vertex3(-1.0f, -1.0f, -1.0f);
GL.Vertex3(1.0f, -1.0f, -1.0f);
GL.Normal3(0.0f, 0.0f, 1.0f);
GL.Vertex3(1.0f, 1.0f, 1.0f);
GL.Vertex3(-1.0f, 1.0f, 1.0f);
GL.Vertex3(-1.0f, -1.0f, 1.0f);
GL.Vertex3(1.0f, -1.0f, 1.0f);
GL.Normal3(0.0f, 0.0f, -1.0f);
GL.Vertex3(1.0f, 1.0f, -1.0f);
GL.Vertex3(1.0f, -1.0f, -1.0f);
GL.Vertex3(-1.0f, -1.0f, -1.0f);
GL.Vertex3(-1.0f, 1.0f, -1.0f);
GL.End();
}
//球を描画する
private void DrawSphere()
{
int slices = 16, stacks = 16; //横と縦の分割数
double r = 1.24; //半径
for (int i = 0; i < stacks; i++)
{
//輪切り上部
double upper = Math.PI / stacks * i;
double upperHeight = Math.Cos(upper);
double upperWidth = Math.Sin(upper);
//輪切り下部
double lower = Math.PI / stacks * (i + 1);
double lowerHeight = Math.Cos(lower);
double lowerWidth = Math.Sin(lower);
GL.Begin(BeginMode.QuadStrip);
for (int j = 0; j <= slices; j++)
{
//輪切りの面を単位円としたときの座標
double rotor = 2 * Math.PI / slices * j;
double x = Math.Cos(rotor);
double y = Math.Sin(rotor);
GL.Normal3(x * lowerWidth, lowerHeight, y * lowerWidth);
GL.Vertex3(r * x * lowerWidth, r * lowerHeight, r * y * lowerWidth);
GL.Normal3(x * upperWidth, upperHeight, y * upperWidth);
GL.Vertex3(r * x * upperWidth, r * upperHeight, r * y * upperWidth);
}
GL.End();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading.Tasks;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
namespace OpenTK_RxUI
{
abstract public class UIElement
{
protected Rectangle rect;
protected Color4 color;
public event EventHandler OnClick;
public UIElement()
{
this.rect = new Rectangle(0, 0, 0, 0);
this.color = Color4.White;
}
public UIElement(Rectangle rect, Color4 color)
{
this.rect = rect;
this.color = color;
}
public bool Contains(Point p)
{
return rect.Contains(p);
}
virtual internal void Draw() { }
virtual internal void Click(object sender)
{
if (OnClick != null)
{
OnClick(sender, EventArgs.Empty);
}
}
}
public class Button : UIElement
{
public Button()
: base()
{
}
public Button(Rectangle rect, Color4 color)
: base(rect, color)
{
}
internal override void Draw()
{
GL.Begin(BeginMode.Quads);
GL.Color4(color);
GL.Vertex2(rect.Left, rect.Top);
GL.Vertex2(rect.Left, rect.Bottom);
GL.Vertex2(rect.Right, rect.Bottom);
GL.Vertex2(rect.Right, rect.Top);
GL.End();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reactive.Linq;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using OpenTK.Graphics.OpenGL;
namespace OpenTK_RxUI
{
//GUIWindow
public class UIWindow : GameWindow
{
protected LinkedList<UIElement> UIElements { get; private set; }
protected IObservable<MouseButtonEventArgs> MouseDownSource { get; private set; }
protected IObservable<MouseMoveEventArgs> MouseMoveSource { get; private set; }
protected IObservable<MouseButtonEventArgs> MouseUpSource { get; private set; }
protected IObservable<MouseWheelEventArgs> MouseWheelChangedSource { get; private set; }
public UIWindow(int width, int height)
: base(width, height)
{
this.UIElements = new LinkedList<UIElement>();
this.MouseDownSource = Observable.FromEvent<EventHandler<MouseButtonEventArgs>, MouseButtonEventArgs>(
h => (s, e) => h(e),
h => this.Mouse.ButtonDown += h,
h => this.Mouse.ButtonDown -= h);
this.MouseMoveSource = Observable.FromEvent<EventHandler<MouseMoveEventArgs>, MouseMoveEventArgs>(
h => (s, e) => h(e),
h => this.Mouse.Move += h,
h => this.Mouse.Move -= h);
this.MouseUpSource = Observable.FromEvent<EventHandler<MouseButtonEventArgs>, MouseButtonEventArgs>(
h => (s, e) => h(e),
h => this.Mouse.ButtonUp += h,
h => this.Mouse.ButtonUp -= h);
this.MouseWheelChangedSource = Observable.FromEvent<EventHandler<MouseWheelEventArgs>, MouseWheelEventArgs>(
h => (s, e) => h(e),
h => this.Mouse.WheelChanged += h,
h => this.Mouse.WheelChanged -= h);
//TODO mouseUpSourceも入れる
this.MouseDownSource.Where(e => e.Button == MouseButton.Left).Subscribe(e =>
{
var ui = UIElements.Where(elem => elem.Contains(e.Position)).FirstOrDefault();
if (ui != null)
{
ui.Click(this);
UIElements.Remove(ui);
UIElements.AddFirst(ui);
}
});
}
//overwrite base.SwapBuffers
public new void SwapBuffers(){
DrawUIElements();
base.SwapBuffers();
}
private void DrawUIElements()
{
Matrix4 modelViewMatrix = Matrix4.LookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitY);
Matrix4 projectionMatrix = Matrix4.CreateOrthographicOffCenter(0, Width, Height, 0, 0.125f, 1.125f);
//退避と設定
GL.PushAttrib(AttribMask.AllAttribBits);
GL.Disable(EnableCap.DepthTest);
GL.Disable(EnableCap.Lighting);
GL.Disable(EnableCap.CullFace);
GL.Viewport(ClientRectangle);
GL.MatrixMode(MatrixMode.Modelview);
GL.PushMatrix();
GL.LoadMatrix(ref modelViewMatrix);
GL.MatrixMode(MatrixMode.Projection);
GL.PushMatrix();
GL.LoadMatrix(ref projectionMatrix);
for (var ui = UIElements.Last; ui != null; ui = ui.Previous)
{
ui.Value.Draw();
}
//元に戻す
GL.MatrixMode(MatrixMode.Projection);
GL.PopMatrix();
GL.MatrixMode(MatrixMode.Modelview);
GL.PopMatrix();
GL.PopAttrib();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment