Can't we just expand the base renderer interface to make it slightly more advanced?
Oh, and I wanted to ask, isn't there something wrong with StartClip/EndClip? the latter hardly ever gets called unlike the former, and it messed up the OpenGL attribute stack when I tried implement clipping by pushing/popping the GL_SCISSOR_BIT attribute to preserve the current clipping region. (which would've been awesome, since every call to EndClip after painting a child would restore the parent's clipping region automatically)
I resorted to saving all attributes at Renderer::Begin() and then restoring everything back when Renderer::End() gets called, but that simply kills the FPS and ruins clipping at different scales.
To be honest, I think I got clipping working by accident in the first place, the whole thing gets highly inconsistent when scaling is involved.
Anyways, here's my SFML (you'll need a current SVN build) renderer draft as it stands:
Code:
#include "Gwen/Gwen.h"
#include "Gwen/BaseRender.h"
#include "Gwen/Utility.h"
#include "Gwen/Font.h"
#include "Gwen/Texture.h"
#include <math.h>
#include <algorithm>
#include <Windows.h>
#include <gl/GL.h>
#include <SFML/Graphics.hpp>
namespace Gwen
{
namespace Renderer
{
class SFML : public Gwen::Renderer::Base
{
private:
template<typename T>
struct Resource
{
private:
T* m_resource;
bool m_delete;
public:
Resource(T* ptr, bool bDelete = false) : m_resource(ptr), m_delete(bDelete) { return; };
~Resource() { if (m_delete) delete(m_resource); };
T* Get() const { return m_resource; };
};
typedef Resource<sf::Font> ResFont;
typedef Resource<sf::Image> ResImage;
public:
typedef sf::Font* (*fontRequestHandler)(const Gwen::UnicodeString& facename, float size, bool& bDelete);
typedef sf::Image* (*imageRequestHandler)(const Gwen::UnicodeString& name, bool& bDelete);
SFML(sf::RenderTarget& target)
: m_renderTarget(target),
m_Color(),
m_onImageRequest(NULL),
m_onFontRequest(NULL)
{
// Lazy programmer's approach to vector rendering..
const static unsigned int missingPixels[] = {
0x80FFFFFF, 0x80000000, 0x80FFFFFF, 0x80000000,
0x80000000, 0x80FFFFFF, 0x80000000, 0x80FFFFFF,
0x80FFFFFF, 0x80000000, 0x80FFFFFF, 0x80000000,
0x80000000, 0x80FFFFFF, 0x80000000, 0x80FFFFFF,
};
m_missingPlaceholder.SetSmooth(false);
m_missingPlaceholder.LoadFromPixels(4, 4, (sf::Uint8*)&missingPixels);
};
virtual ~SFML()
{
};
virtual void Begin()
{
m_renderTarget.SaveGLStates();
};
virtual void End()
{
m_renderTarget.RestoreGLStates();
};
virtual void StartClip()
{
Gwen::Rect rect = ClipRegion();
Gwen::Point offset = GetRenderOffset();
float scale = Scale();
//DrawDebugRect(rect);
GLint viewportData[4];
glGetIntegerv(GL_VIEWPORT, &viewportData[0]);
int vx = viewportData[0], vy = viewportData[1], vw = viewportData[2], vh = viewportData[3];
int x = floor(float(rect.x) * scale),
y = floor(float(rect.y + rect.h) * scale),
w = floor(float(rect.w) * scale),
h = floor(float(rect.h) * scale);
// we must swap the Y axis for glScissor to work.
glScissor(x, vh - y, w, h);
glEnable(GL_SCISSOR_TEST);
};
virtual void EndClip()
{
glDisable(GL_SCISSOR_TEST);
};
virtual void SetDrawColor( Gwen::Color color )
{
m_Color = toSfColor(color);
};
virtual void DrawLine( int x, int y, int a, int b )
{
Translate(x, y);
Translate(a, b);
sf::Shape line = sf::Shape::Line(x, y, a, b, 1.0f, m_Color);
m_renderTarget.Draw(line);
};
virtual void DrawFilledRect( Gwen::Rect rect )
{
Translate(rect);
sf::Shape shape = sf::Shape::Rectangle(
0,
0,
rect.w - 1,
rect.h - 1,
m_Color
);
shape.SetPosition(rect.x, rect.y);
m_renderTarget.Draw(shape);
};
virtual void DrawPixel( int x, int y )
{
Translate(x, y);
//glPointSize(Scale());
glBegin(GL_POINTS);
glColor4f(m_Color.r / 255.0f, m_Color.g / 255.0f, m_Color.b /255.0f, m_Color.a / 255.0f);
glVertex2f(x, y);
glEnd();
};
virtual void DrawLinedRect( Gwen::Rect rect )
{
Translate(rect);
sf::Shape shape = sf::Shape::Rectangle(
0,
0,
rect.w-2,
rect.h-2,
m_Color,
1.0f,
m_Color
);
shape.EnableFill(false);
shape.EnableOutline(true);
shape.SetPosition(rect.x+1, rect.y+1);
m_renderTarget.Draw(shape);
};
virtual void DrawShavedCornerRect( Gwen::Rect rect, bool bSlight = false )
{
sf::Shape shape;
float cornerSize = bSlight ? 1.0f : 2.0f;
Translate(rect);
rect.x += 1; rect.y += 1;
rect.w -= 2; rect.h -= 2;
shape.SetPosition(rect.x, rect.y);
shape.SetOutlineWidth(1.0f);
shape.EnableFill(false);
shape.EnableOutline(true);
shape.AddPoint(cornerSize, 0, m_Color, m_Color);
shape.AddPoint(rect.w - cornerSize, 0, m_Color, m_Color);
shape.AddPoint(rect.w, cornerSize, m_Color, m_Color);
shape.AddPoint(rect.w, rect.h - cornerSize, m_Color, m_Color);
shape.AddPoint(rect.w - cornerSize, rect.h, m_Color, m_Color);
shape.AddPoint(cornerSize, rect.h, m_Color, m_Color);
shape.AddPoint(0, rect.h - cornerSize, m_Color, m_Color);
shape.AddPoint(0, cornerSize, m_Color, m_Color);
m_renderTarget.Draw(shape);
};
virtual void DrawTexturedRect( Gwen::Texture* pTexture, Gwen::Rect pTargetRect,
float u1=0.0f, float v1=0.0f, float u2=1.0f, float v2=1.0f )
{
if (!pTexture->data)
return DrawMissingImage(pTargetRect);
Translate(pTargetRect);
ResImage* res = static_cast<ResImage*>(pTexture->data);
const sf::Image* tex = res->Get();
sf::Sprite sp(*tex);
sf::IntRect rect = toSfRect(pTargetRect);
float fW = tex->GetWidth(), fH = tex->GetHeight();
sp.SetSubRect(sf::IntRect(ceil(u1 * fW), ceil(v1 * fH), ceil(u2 * fW), ceil(v2 * fH)));
sp.SetPosition(sf::Vector2f(pTargetRect.x, pTargetRect.y));
sp.Resize(sf::Vector2f(pTargetRect.w, pTargetRect.h));
m_renderTarget.Draw(sp);
};
virtual void DrawMissingImage( Gwen::Rect pTargetRect )
{
Translate(pTargetRect);
sf::Sprite missing = sf::Sprite(m_missingPlaceholder);
missing.SetPosition(pTargetRect.x, pTargetRect.y);
missing.Resize(pTargetRect.w, pTargetRect.h);
m_renderTarget.Draw(missing);
}
// Text:
template<typename T>
void RenderText( Gwen::Font* pFont, Gwen::Point pos, const T& text )
{
if (!pFont)
return;
else if (!pFont->data)
LoadFont(pFont);
Translate(pos.x, pos.y);
float scale = Scale();
ResFont* res = static_cast<ResFont*>(pFont->data);
int realsize = (ceil(pFont->size * scale) / 4 == 1) ? ceil(pFont->size * scale) : floor(pFont->size * scale);
sf::Text txt(sf::String(text), *res->Get(), realsize);
txt.SetColor(m_Color);
txt.SetPosition(toSfVectorF(pos));
m_renderTarget.Draw(txt);
};
template<typename T>
Gwen::Point MeasureText(Gwen::Font* pFont, const T& text)
{
if (!pFont)
return Gwen::Point();
else if (!pFont->data)
LoadFont(pFont);
float scale = Scale();
ResFont* res = static_cast<ResFont*>(pFont->data);
int realsize = (ceil(pFont->size * scale) / 4 == 1) ? ceil(pFont->size * scale) : floor(pFont->size * scale);
sf::Text txt(sf::String(text), *res->Get(), realsize);
if (text.empty())
txt.SetString(" ");
sf::Vector2f sz = txt.GetRect().GetSize();
return Gwen::Point(sz.x, sz.y);
};
virtual void RenderText( Gwen::Font* pFont, Gwen::Point pos, const Gwen::String& text )
{ return RenderText<Gwen::String>(pFont, pos, text); };
virtual void RenderText( Gwen::Font* pFont, Gwen::Point pos, const Gwen::UnicodeString& text )
{ return RenderText<Gwen::UnicodeString>(pFont, pos, text); };
virtual Gwen::Point MeasureText( Gwen::Font* pFont, const Gwen::String& text )
{ return MeasureText<Gwen::String>(pFont, text); };
virtual Gwen::Point MeasureText( Gwen::Font* pFont, const Gwen::UnicodeString& text )
{ return MeasureText<Gwen::UnicodeString>(pFont, text); };
// Resource handling
virtual void LoadTexture( Gwen::Texture* pTexture )
{
if (!pTexture)
return;
if (pTexture->data)
FreeTexture(pTexture);
sf::Image* tex = NULL;
bool bDelete = false;
if (m_onImageRequest)
{
tex = m_onImageRequest(pTexture->name, bDelete);
}
else
{
tex = new sf::Image();
tex->SetSmooth(true);
if (!tex || !tex->LoadFromFile(Gwen::Utility::UnicodeToString(pTexture->name)))
{
if (tex)
delete(tex);
pTexture->failed = true;
return;
}
else
{
bDelete = true;
}
}
pTexture->height = tex->GetHeight();
pTexture->width = tex->GetWidth();
pTexture->data = new ResImage(tex, bDelete);
};
virtual void FreeTexture( Gwen::Texture* pTexture )
{
if (pTexture->data)
delete (static_cast<ResImage*>(pTexture->data));
pTexture->data = NULL;
return;
};
virtual void LoadFont( Gwen::Font* pFont )
{
if (!pFont)
return;
sf::Font* font = NULL;
bool bDelete = false;
if (m_onFontRequest)
{
font = m_onFontRequest(pFont->facename, pFont->size, bDelete);
}
else
{
font = new sf::Font();
std::string facename(Gwen::Utility::UnicodeToString(pFont->facename));
if (!font->LoadFromFile(facename))
{
font = (sf::Font*)(void*)&sf::Font::GetDefaultFont();
bDelete = false;
}
else
{
bDelete = true;
}
}
pFont->data = new ResFont(font, bDelete);
pFont->realsize = Scale();
};
virtual void FreeFont( Gwen::Font* pFont )
{
if (pFont && pFont->data)
{
delete (static_cast<ResFont*>(pFont->data));
pFont->data = NULL;
}
};
// Request hooks:
fontRequestHandler FontRequest() const { return m_onFontRequest; }
void FontRequest(fontRequestHandler val) { m_onFontRequest = val; }
imageRequestHandler ImageRequest() const { return m_onImageRequest; }
void ImageRequest(imageRequestHandler val) { m_onImageRequest = val; }
private:
sf::RenderTarget& m_renderTarget;
sf::Color m_Color;
fontRequestHandler m_onFontRequest;
imageRequestHandler m_onImageRequest;
// Internal resources:
sf::Image m_missingPlaceholder;
// Helpers
static sf::Color toSfColor(const Gwen::Color& c)
{
return sf::Color(c.r,c.g,c.b,c.a);
};
static sf::IntRect toSfRect(const Gwen::Rect& r)
{
return sf::IntRect(r.x, r.y, r.x + r.w, r.y + r.h);
};
static sf::FloatRect toSfRectF(const Gwen::Rect& r)
{
return sf::FloatRect(r.x, r.y, r.x + r.w, r.y + r.h);
};
static sf::Vector2i toSfVector(const Gwen::Point p)
{
return sf::Vector2i(p.x, p.y);
};
static sf::Vector2f toSfVectorF(const Gwen::Point p)
{
return sf::Vector2f(p.x, p.y);
};
void DrawDebugRect( Gwen::Rect rect, bool translate = true )
{
if (translate)
Translate(rect);
sf::Shape shape = sf::Shape::Rectangle(
0,
0,
rect.w-2,
rect.h-2,
m_Color,
1.0f,
sf::Color::Red
);
shape.EnableFill(false);
shape.EnableOutline(true);
shape.SetPosition(rect.x+1, rect.y+1);
m_renderTarget.Draw(shape);
};
};
}
}
And the sample:
Code:
#include <SFML/Graphics.hpp>
#include "Gwen/Gwen.h"
#include "Gwen/Skins/Simple.h"
#include "Gwen/Skins/TexturedBase.h"
#include "Gwen/UnitTest/UnitTest.h"
#include "Gwen/Renderers/SFML.h"
//#include "Gwen/Renderers/SFML_Drawable.h"
#pragma comment( lib, "sfml-window-s-d.lib" )
#pragma comment( lib, "sfml-graphics-s-d.lib" )
#pragma comment( lib, "sfml-system-s-d.lib" )
#pragma comment( lib, "sfml-main-d.lib" )
void ProcessEvent(sf::Event& event);
//sf::Font* fontRequestHandler(const Gwen::UnicodeString& facename, float size, bool& bDelete)
//{
// sf::Font* font = new sf::Font();
// std::string name = Gwen::Utility::UnicodeToString(facename);
// name.append(".ttf");
//
// if (font->LoadFromFile(name))
// {
// bDelete = true;
// return font;
// }
// else
// {
// bDelete = false;
// return (sf::Font*)(void*)&sf::Font::GetDefaultFont();
// }
//}
int main()
{
sf::RenderWindow window(sf::VideoMode(1016, 538), "SFML window");
Gwen::Renderer::SFML render(window);
//Gwen::Renderer::SFMLDrawable render();
Gwen::Skin::TexturedBase skin;
//Gwen::Skin::Simple skin;
//render.FontRequest(&fontRequestHandler);
skin.SetRender( &render );
skin.Init();
Gwen::Controls::Canvas* pCanvas = new Gwen::Controls::Canvas( &skin );
pCanvas->SetSize( 1000, 500);
UnitTest* pUnit = new UnitTest( pCanvas );
pUnit->SetPos( 10, 10 );
//Gwen::Controls::Button* x = new Gwen::Controls::Button(pCanvas);
//x->SetPos(100, 100);
//x->SetSize(140, 60);
//x->SetText("Hello world!");
window.SetActive();
while (window.IsOpened())
{
sf::Event event;
while (window.GetEvent(event))
{
switch (event.Type)
{
case sf::Event::Closed:
window.Close();
break;
case sf::Event::Resized:
pCanvas->SetScale((float)window.GetWidth() / (float)pCanvas->Width());
break;
default:
ProcessEvent(event);
break;
}
}
window.Clear(sf::Color(150, 170, 170)); // clear the window
pCanvas->DoInput();
pCanvas->RenderCanvas();
//window.Draw(render);
window.Display(); // swap screen buffers
}
return EXIT_SUCCESS;
};
void ProcessEvent(sf::Event& event)
{
switch(event.Type)
{
case sf::Event::MouseMoved:
Gwen::Input::CursorMoved(event.MouseMove.X, event.MouseMove.Y);
break;
case sf::Event::MouseButtonPressed:
Gwen::Input::MouseButton(event.MouseButton.Button, true);
break;
case sf::Event::MouseButtonReleased:
Gwen::Input::MouseButton(event.MouseButton.Button, false);
case sf::Event::MouseWheelMoved:
Gwen::Input::MouseWheel(event.MouseWheel.Delta * 120);
break;
case sf::Event::TextEntered:
Gwen::Input::Character(event.Text.Unicode);
break;
case sf::Event::KeyPressed:
case sf::Event::KeyReleased:
{
bool pressed = (event.Type == sf::Event::KeyPressed);
int keyCode = event.Key.Code;
int iKey = -1;
if (event.Key.Control)
{
if (keyCode == sf::Key::C)
{
Gwen::Input::ActionMessage(Gwen::Input::Message::Copy);
break;
}
else if (keyCode == sf::Key::V)
{
Gwen::Input::ActionMessage(Gwen::Input::Message::Paste);
break;
}
else if (keyCode == sf::Key::X)
{
Gwen::Input::ActionMessage(Gwen::Input::Message::Cut);
break;
}
else if (keyCode == sf::Key::A)
{
Gwen::Input::ActionMessage(Gwen::Input::Message::SelectAll);
break;
}
else if (keyCode == sf::Key::Z)
{
Gwen::Input::ActionMessage(Gwen::Input::Message::Undo);
break;
}
else if (keyCode == sf::Key::Y)
{
Gwen::Input::ActionMessage(Gwen::Input::Message::Redo);
break;
}
}
if (keyCode == sf::Key::Back) iKey = Gwen::Key::Backspace;
else if (keyCode == sf::Key::Return) iKey = Gwen::Key::Return;
else if (keyCode == sf::Key::Escape) iKey = Gwen::Key::Escape;
else if (keyCode == sf::Key::Tab) iKey = Gwen::Key::Tab;
else if (keyCode == sf::Key::Space) iKey = Gwen::Key::Space;
else if (keyCode == sf::Key::Up) iKey = Gwen::Key::Up;
else if (keyCode == sf::Key::Down) iKey = Gwen::Key::Down;
else if (keyCode == sf::Key::Left) iKey = Gwen::Key::Left;
else if (keyCode == sf::Key::Right) iKey = Gwen::Key::Right;
else if (keyCode == sf::Key::Home) iKey = Gwen::Key::Home;
else if (keyCode == sf::Key::Delete) iKey = Gwen::Key::Delete;
else if (keyCode == sf::Key::LControl || keyCode == sf::Key::RControl) iKey = Gwen::Key::Control;
else if (keyCode == sf::Key::LAlt || keyCode == sf::Key::RAlt) iKey = Gwen::Key::Alt;
else if (keyCode == sf::Key::LShift || keyCode == sf::Key::RShift) iKey = Gwen::Key::Shift;
if (iKey != -1)
{
if (pressed)
Gwen::Input::KeyPress(iKey);
else
Gwen::Input::KeyRelease(iKey);
}
break;
}
default:
break;
}
};
I'm currently rewriting the whole thing as a sf::Drawable with a vertex pipeline, which means that no drawing will take place until window.Draw(render) is called in the main loop, should give a hefty performance boost.