CSharpGL(26)在opengl中实现控件布局/渲染文字
CSharpGL(26)在opengl中实现控件布局/渲染文字
效果图
如图所示,可以将文字、坐标轴固定在窗口的一角。
下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
UI控件布局关键点
ILayout
类似Winform控件那样,控件的位置、大小由其Anchor等属性决定。窗口大小改变时,控件的位置、大小会随之改变。
所以模仿Control类,直接使用Anchor作为UIRenderer的接口。
/// <summary>
/// Supports layout UI element in OpenGL canvas.
/// 实现在OpenGL窗口中的UI布局
/// </summary>
public interface ILayout : ITreeNode<UIRenderer>
{
//event EventHandler afterLayout; /// <summary>
/// the edges of the <see cref="GLCanvas"/> to which a UI’s rect is bound and determines how it is resized with its parent.
/// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para>
/// </summary>
System.Windows.Forms.AnchorStyles Anchor { get; set; } /// <summary>
/// Gets or sets the space between viewport and SimpleRect.
/// </summary>
System.Windows.Forms.Padding Margin { get; set; } /// <summary>
/// 相对于Parent左下角的位置(Left Down location)
/// </summary>
System.Drawing.Point Location { get; set; } /// <summary>
/// Stores width when <see cref="Anchor"/>.Left & <see cref="Anchor"/>.Right is <see cref="Anchor"/>.None.
/// <para> and height when <see cref="Anchor"/>.Top & <see cref="Anchor"/>.Bottom is <see cref="Anchor"/>.None.</para>
/// </summary>
System.Drawing.Size Size { get; set; } /// <summary>
///
/// </summary>
System.Drawing.Size ParentLastSize { get; set; } /// <summary>
///
/// </summary>
int zNear { get; set; } /// <summary>
///
/// </summary>
int zFar { get; set; } }
实现在OpenGL窗口中的UI布局
有了数据结构,就可以实现窗口中的UI布局了。当窗口大小改变时,调用下面的函数。
/// <summary>
/// layout controls in OpenGL canvas.
/// <para>This coordinate system is as below.</para>
/// <para> /\ y</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |----------------->x</para>
/// <para>(0, 0)</para>
/// </summary>
/// <param name="uiRenderer"></param>
internal static void Layout(this ILayout uiRenderer)
{
ILayout parent = uiRenderer.Parent;
if (parent != null)
{
uiRenderer.Self.DoBeforeLayout();
NonRootNodeLayout(uiRenderer, parent);
uiRenderer.Self.DoAfterLayout();
} foreach (var item in uiRenderer.Children)
{
item.Layout();
} if (parent != null)
{
uiRenderer.ParentLastSize = parent.Size;
}
} /// <summary>
/// leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
/// </summary>
private const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right); /// <summary>
/// topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
/// </summary>
private const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom); /// <summary>
/// Gets <paramref name="currentNode"/>'s location and size according to its state and parent's information.
/// </summary>
/// <param name="currentNode"></param>
/// <param name="parent"></param>
private static void NonRootNodeLayout(ILayout currentNode, ILayout parent)
{
int x, y, width, height;
if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
{
width = parent.Size.Width - currentNode.Margin.Left - currentNode.Margin.Right;
//width = currentNode.Size.Width + (parent.Size.Width - currentNode.ParentLastSize.Width);
if (width < ) { width = ; }
}
else
{
width = currentNode.Size.Width;
} if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
{
height = parent.Size.Height - currentNode.Margin.Top - currentNode.Margin.Bottom;
//height = currentNode.Size.Height + (parent.Size.Height - currentNode.ParentLastSize.Height);
if (height < ) { height = ; }
}
else
{
height = currentNode.Size.Height;
} if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.None)
{
x = (int)(
(parent.Size.Width - width)
* ((double)currentNode.Margin.Left / (double)(currentNode.Margin.Left + currentNode.Margin.Right)));
}
else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Left)
{
x = parent.Location.X + currentNode.Margin.Left;
}
else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Right)
{
x = parent.Location.X + parent.Size.Width - currentNode.Margin.Right - width;
}
else if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
{
x = parent.Location.X + currentNode.Margin.Left;
}
else
{ throw new Exception("uiRenderer should not happen!"); } if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.None)
{
y = (int)(
(parent.Size.Height - height)
* ((double)currentNode.Margin.Bottom / (double)(currentNode.Margin.Bottom + currentNode.Margin.Top)));
}
else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Bottom)
{
//y = currentNode.Margin.Bottom;
y = parent.Location.Y + currentNode.Margin.Bottom;
}
else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Top)
{
//y = parent.Size.Height - height - currentNode.Margin.Top;
y = parent.Location.Y + parent.Size.Height - currentNode.Margin.Top - height;
}
else if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
{
//y = currentNode.Margin.Top + parent.Location.Y;
y = parent.Location.Y + currentNode.Margin.Bottom;
}
else
{ throw new Exception("This should not happen!"); } currentNode.Location = new System.Drawing.Point(x, y);
currentNode.Size = new Size(width, height);
}
public static void Layout(this ILayout uiRenderer)
glViewport/glScissor
这是避免复杂的矩阵操作,实现稳定的UI布局显示的关键。glViewport指定了GLRenderer在窗口的渲染位置,glScissor将GLRenderer范围之外的部分保护起来。
在渲染之前,根据UIRenderer的位置和大小更新viewport和scissor即可。不再需要为UI固定在窗口某处而煞费苦心地设计projection,view,model矩阵了。
/// <summary>
/// Renderer that supports UI layout.
/// 支持2D UI布局的渲染器
/// </summary>
public class UIRenderer : RendererBase, ILayout
{
private ViewportSwitch viewportSwitch;
private ScissorTestSwitch scissorTestSwitch;
private GLSwitchList switchList = new GLSwitchList(); /// <summary>
///
/// </summary>
public GLSwitchList SwitchList
{
get { return switchList; }
} /// <summary>
/// triggered before layout in <see cref="ILayout"/>.Layout().
/// </summary>
public event EventHandler BeforeLayout;
/// <summary>
/// triggered after layout in <see cref="ILayout"/>.Layout().
/// </summary>
public event EventHandler AfterLayout; internal void DoBeforeLayout()
{
EventHandler BeforeLayout = this.BeforeLayout;
if (BeforeLayout != null)
{
BeforeLayout(this, null);
}
} internal void DoAfterLayout()
{
EventHandler AfterLayout = this.AfterLayout;
if (AfterLayout != null)
{
AfterLayout(this, null);
}
} /// <summary>
///
/// </summary>
public RendererBase Renderer { get; protected set; }
/// <summary>
///
/// </summary>
/// <param name="anchor"></param>
/// <param name="margin"></param>
/// <param name="size"></param>
/// <param name="zNear"></param>
/// <param name="zFar"></param>
public UIRenderer(
System.Windows.Forms.AnchorStyles anchor, System.Windows.Forms.Padding margin,
System.Drawing.Size size, int zNear, int zFar)
{
this.Children = new ChildList<UIRenderer>(this);// new ILayoutList(this); this.Anchor = anchor; this.Margin = margin;
this.Size = size; this.zNear = zNear; this.zFar = zFar;
} /// <summary>
///
/// </summary>
public System.Windows.Forms.AnchorStyles Anchor { get; set; } /// <summary>
///
/// </summary>
public System.Windows.Forms.Padding Margin { get; set; } /// <summary>
///
/// </summary>
public System.Drawing.Point Location { get; set; } /// <summary>
///
/// </summary>
public System.Drawing.Size Size { get; set; }
/// <summary>
///
/// </summary>
public System.Drawing.Size ParentLastSize { get; set; } /// <summary>
///
/// </summary>
public int zNear { get; set; } /// <summary>
///
/// </summary>
public int zFar { get; set; } /// <summary>
///
/// </summary>
protected override void DoInitialize()
{
this.viewportSwitch = new ViewportSwitch();
this.scissorTestSwitch = new ScissorTestSwitch(); RendererBase renderer = this.Renderer;
if (renderer != null)
{
renderer.Initialize();
}
} /// <summary>
///
/// </summary>
/// <param name="arg"></param>
protected override void DoRender(RenderEventArg arg)
{
this.viewportSwitch.X = this.Location.X;
this.viewportSwitch.Y = this.Location.Y;
this.viewportSwitch.Width = this.Size.Width;
this.viewportSwitch.Height = this.Size.Height;
this.scissorTestSwitch.X = this.Location.X;
this.scissorTestSwitch.Y = this.Location.Y;
this.scissorTestSwitch.Width = this.Size.Width;
this.scissorTestSwitch.Height = this.Size.Height; this.viewportSwitch.On();
this.scissorTestSwitch.On();
int count = this.switchList.Count;
for (int i = ; i < count; i++) { this.switchList[i].On(); } // 把所有在此之前渲染的内容都推到最远。
// Push all rendered stuff to farest position.
OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT); RendererBase renderer = this.Renderer;
if (renderer != null)
{
renderer.Render(arg);
} for (int i = count - ; i >= ; i--) { this.switchList[i].Off(); }
this.scissorTestSwitch.Off();
this.viewportSwitch.Off();
} /// <summary>
///
/// </summary>
protected override void DisposeUnmanagedResources()
{
RendererBase renderer = this.Renderer;
if (renderer != null)
{
renderer.Dispose();
}
} /// <summary>
///
/// </summary>
public UIRenderer Self { get { return this; } } /// <summary>
///
/// </summary>
public UIRenderer Parent { get; set; } //ChildList<UIRenderer> children; /// <summary>
///
/// </summary>
[Editor(typeof(IListEditor<UIRenderer>), typeof(UITypeEditor))]
public ChildList<UIRenderer> Children { get; private set; }
}
UIRenderer
叠加/覆盖
注意在UIRenderer.DoRender(RenderEventArgs arg)中,使用
// 把所有在此之前渲染的内容都推到最远。
// Push all rendered stuff to farest position.
OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT);
把所有在此之前渲染的内容都推到最远。
从ILayout的定义中可以看到,控件与控件组成了一个树结构。其根结点是覆盖整个窗口的控件,在渲染UI时处于第一个渲染的位置,然后渲染它的各个子结点代表的控件。这就实现了子控件能够完全覆盖在父控件之上。
我突然想到了WPF。
渲染文字
从TTF文件获取字形
(https://github.com/MikePopoloski/SharpFont)是一个纯C#的解析TTF文件的库,能够代替C++的FreeType。我将其稍作修改,实现了从TTF文件获取任意uncode字形,进而获取字形纹理,实现渲染文字的功能。
例如下面这几个字形纹理。
使用FontResource
FontResource类型封装了使用字形贴图的功能。
使用方式也非常简单。首先创建一个字体资源对象。
FontResource fontResouce = FontResource.Load(ttfFilename, ' ', (char));
然后交给GLText。
var glText = new GLText(AnchorStyles.Left | AnchorStyles.Top,
new Padding(, , , ), new Size(, ), -, , fontResouce);
glText.Initialize();
glText.SetText("The quick brown fox jumps over the lazy dog!");
GLText在初始化时指定此字体对象包含的二维纹理。
protected override void DoInitialize()
{
base.DoInitialize(); this.Renderer.SetUniform("fontTexture", this.fontResource.GetSamplerValue());
}
2016-07-30
现在我已经废弃了FontResource,改用更简单的实现方式(FontTexture)。
FontResource需要通过复杂的SharpFont来自行解析TTF文件。我至今没有详细看过SharpFont的代码,因为SharpFont实在太大了。而FontTexture直接借助System.Drawing.Font类型的Font.MeasureString()方法来获取字形的大小,并且可以通过Graphics.DrawString()把字形贴到 Bitmap 对象上。这就解决了获取文字贴图及其UV字典的问题。
不得不说.net framework自带类库的功能之丰富,简直富可敌国。
2016-8-3
如何创建一个对象,然后用UI的方式渲染?
创建一个对象SomeRenderer时,像普通对象一样,用IBufferable+Renderer的方式创建模型和渲染器(或者用RendererBase,这可以使用Legacy OpenGL)。注意,模型的边界应该是(-0.5, -0.5, -0.5)到(0.5, 0.5, 0.5),即边长为(1, 1, 1)且中心在原点的立方体。如此一来,就可以在SomeRenderer的DoRender()方法里指定对象的缩放比例为:
mat4 model = glm.scale(mat4.identity(), new vec3(this.Size.Width, this.Size.Height, ));
这样的缩放比例就可以恰好使得SomeRenderer的模型填满UI的矩形范围。
总结
CSharpGL支持控件布局,支持渲染文字了。
欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)
CSharpGL(26)在opengl中实现控件布局/渲染文字的更多相关文章
- CSharpGL(6)在OpenGL中绘制UI元素
CSharpGL(6)在OpenGL中绘制UI元素 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo,更适合入 ...
- wxPython中基本控件学习
wxPython工具包提供了多种不同的窗口部件,包括了本章所提到的基本控件.我们涉及静态文本.可编辑的文本.按钮.微调.滑块.复选框.单选按钮.选择器.列表框.组合框和标尺.对于每种窗口部件,我们将提 ...
- WPF中TreeView控件SelectedItemChanged方法的MVVM绑定
问题描述:左侧treeview控件中点击不同类别的节点时,右侧的页面会显示不同的权限.比如对于My Publications,拥有Modify和Delete两种权限,对于My Subscription ...
- WPF中Ribbon控件的使用
这篇博客将分享如何在WPF程序中使用Ribbon控件.Ribbon可以很大的提高软件的便捷性. 上面截图使Outlook 2010的界面,在Home标签页中,将所属的Menu都平铺的布局,非常容易的可 ...
- Android线程中设置控件
在Android中经常出现多线程中设置控件的值报错的情况,今天教大家封装一个简单的类避免这样的问题,同样也调用实现也非常的方便. 自定义类: /** * Created by wade on 2016 ...
- [转载]ASP.NET中TextBox控件设立ReadOnly="true"后台取不到值
原文地址:http://www.cnblogs.com/yxyht/archive/2013/03/02/2939883.html ASP.NET中TextBox控件设置ReadOnly=" ...
- Android中ListView控件的使用
Android中ListView控件的使用 ListView展示数据的原理 在Android中,其实ListView就相当于web中的jsp,Adapter是适配器,它就相当于web中的Servlet ...
- .net dataGridView当鼠标经过时当前行背景色变色;然后【给GridView增加单击行事件,并获取单击行的数据填充到页面中的控件中】
1.首先在前台dataGridview属性中增加onRowDataBound属性事件 2.然后在后台Observing_RowDataBound事件中增加代码 protected void Obser ...
- 041. asp.net中内容页访问母版页中的控件
母版页运行机制: 用户通过输入内容也的URL来请求某个页面, 获取该页面后, 读取@Page指令, 如果该指令引用了一个母版页, 则也读取该母版页, 如果也是第一次请求这两个页面, 则母版页和被请求的 ...
随机推荐
- Jenkins 安装的HTML Publisher Plugin 插件无法展示ant生成的JunitReport报告
最近在做基于jenkins ant junit 的测试持续集成,单独ant junit生成的junitreport报告打开正常,使用Jenkins的HTML Publisher Plugin 插件无 ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- Kotlin的Lambda表达式以及它们怎样简化Android开发(KAD 07)
作者:Antonio Leiva 时间:Jan 5, 2017 原文链接:https://antonioleiva.com/lambdas-kotlin/ 由于Lambda表达式允许更简单的方式建模式 ...
- 调用AJAX做登陆和注册
先建立一个页面来检测一下我们建立的用户名能不能用,看一下有没有已经存在的用户名吗 可以通过ajax提示一下 $("#uid").blur(function(){ //取用户名 va ...
- 【原】FMDB源码阅读(二)
[原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...
- MVC如何使用开源分页插件shenniu.pager.js
最近比较忙,前期忙公司手机端接口项目,各种开发+调试+发布现在几乎上线无问题了:虽然公司项目忙不过在期间抽空做了两件个人觉得有意义的事情,一者使用aspnetcore开发了个人线上项目(要说线上其实只 ...
- .net erp(办公oa)开发平台架构概要说明之表单设计器
背景:搭建一个适合公司erp业务的开发平台. 架构概要图: 表单设计开发部署示例图 表单设计开发部署示例说明1)每个开发人员可以自己部署表单设计至本地一份(当然也可以共用一套开发环境,但是如 ...
- Visual Studio 2012远程调试中遇到的问题
有的时候开发环境没问题的代码在生产环境中会某些开发环境无法重现的问题,或者需要对生产环境代码进行远程调试该怎么办? Vs已经提供给开发者远程调试的工具 下面简单讲讲该怎么用,前期准备:1.本地登录账户 ...
- pt-online-schema-change中update触发器的bug
pt-online-schema-change在对表进行表结构变更时,会创建三个触发器. 如下文测试案例中的t2表,表结构如下: mysql> show create table t2\G . ...
- Nginx反向代理,负载均衡,redis session共享,keepalived高可用
相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tomcat服务器两台,由nginx进行反向代理和负载均衡,此 ...