Skip to content

Commit

Permalink
UI|Client|Oculus Rift: Draw a custom mouse cursor in Oculus Rift mode
Browse files Browse the repository at this point in the history
The cursor is simply a LabelWidget that shows the old mouse cursor
image. It is moved to the coordinates of the real mouse cursor.
  • Loading branch information
skyjake committed Dec 21, 2013
1 parent c9f878f commit 4c3aeac
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 20 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doomsday/client/data/defaultstyle.pack/images.dei
Expand Up @@ -4,6 +4,7 @@ group window {
image background { path = "graphics/background.jpg" }
image borderglow { path = "graphics/borderglow.png" }
image icon { path = "graphics/window.png" }
image cursor { path = "graphics/mouse.png" }
}

group logo {
Expand Down
3 changes: 3 additions & 0 deletions doomsday/client/include/ui/windowsystem.h
Expand Up @@ -25,6 +25,7 @@

#include <de/System>
#include <de/String>
#include <de/Vector>
#include "SettingsRegister"

class ClientWindow;
Expand Down Expand Up @@ -105,6 +106,8 @@ class WindowSystem : public de::System
*/
void dispatchLatestMousePosition();

de::Vector2i latestMousePosition() const;

// System.
bool processEvent(de::Event const &);
void timeChanged(de::Clock const &);
Expand Down
92 changes: 73 additions & 19 deletions doomsday/client/src/ui/clientwindow.cpp
Expand Up @@ -28,6 +28,7 @@
#include "clientapp.h"
#include <de/DisplayMode>
#include <de/NumberValue>
#include <de/ConstantRule>
#include <QGLFormat>
#include <de/GLState>
#include <de/GLFramebuffer>
Expand Down Expand Up @@ -82,6 +83,10 @@ DENG2_OBSERVES(App, StartupComplete)
GameSelectionWidget *gameSelMenu;
BusyWidget *busy;
GuiWidget *sidebar;
LabelWidget *cursor;
ConstantRule *cursorX;
ConstantRule *cursorY;
bool cursorHasBeenHidden;

// FPS notifications.
LabelWidget *fpsCounter;
Expand All @@ -91,24 +96,28 @@ DENG2_OBSERVES(App, StartupComplete)
VRWindowTransform contentXf;

Instance(Public *i)
: Base(i),
needMainInit(true),
needRecreateCanvas(false),
needRootSizeUpdate(false),
mode(Normal),
root(thisPublic),
compositor(0),
game(0),
gameUI(0),
taskBar(0),
notifications(0),
colorAdjust(0),
background(0),
gameSelMenu(0),
sidebar(0),
fpsCounter(0),
oldFps(0),
contentXf(*i)
: Base(i)
, needMainInit(true)
, needRecreateCanvas(false)
, needRootSizeUpdate(false)
, mode(Normal)
, root(thisPublic)
, compositor(0)
, game(0)
, gameUI(0)
, taskBar(0)
, notifications(0)
, colorAdjust(0)
, background(0)
, gameSelMenu(0)
, sidebar(0)
, cursor(0)
, cursorX(new ConstantRule(0))
, cursorY(new ConstantRule(0))
, cursorHasBeenHidden(false)
, fpsCounter(0)
, oldFps(0)
, contentXf(*i)
{
/// @todo The decision whether to receive input notifications from the
/// canvas is really a concern for the input drivers.
Expand All @@ -130,6 +139,9 @@ DENG2_OBSERVES(App, StartupComplete)
self.canvas().audienceForFocusChange -= this;
self.canvas().audienceForMouseStateChange -= this;
self.canvas().audienceForKeyEvent -= this;

releaseRef(cursorX);
releaseRef(cursorY);
}

Widget &container()
Expand Down Expand Up @@ -215,6 +227,17 @@ DENG2_OBSERVES(App, StartupComplete)
root.add(colorAdjust);

taskBar->hide();

// Mouse cursor is used with transformed content.
cursor = new LabelWidget;
cursor->margins().set(""); // no margins
cursor->setImage(style.images().image("window.cursor"));
cursor->setAlignment(ui::AlignTopLeft);
cursor->rule()
.setSize(Const(48), Const(48))
.setLeftTop(*cursorX, *cursorY);
cursor->hide();
container().add(cursor);
}

void appStartupCompleted()
Expand Down Expand Up @@ -515,6 +538,7 @@ DENG2_OBSERVES(App, StartupComplete)
if(sidebar) container().remove(*sidebar);
container().remove(*notifications);
container().remove(*taskBar);
container().remove(*cursor);

if(enable && !compositor)
{
Expand All @@ -540,6 +564,7 @@ DENG2_OBSERVES(App, StartupComplete)
if(sidebar) container().add(sidebar);
container().add(notifications);
container().add(taskBar);
container().add(cursor);

if(mode == Normal)
{
Expand All @@ -555,14 +580,37 @@ DENG2_OBSERVES(App, StartupComplete)

if(VR::mode() == VR::MODE_OCULUS_RIFT)
{
compositor->setCompositeProjection(Matrix4f::ortho(-1, 2, -1, 2));
compositor->setCompositeProjection(Matrix4f::ortho(-1.1f, 2.2f, -1.1f, 2.2f));
}
else
{
// We'll simply cover the entire view.
compositor->useDefaultCompositeProjection();
}
}

void updateMouseCursor()
{
// The cursor is only needed if the content is warped.
cursor->show(!self.canvas().isMouseTrapped() && VR::mode() == VR::MODE_OCULUS_RIFT);

if(cursor->isVisible())
{
if(!cursorHasBeenHidden)
{
qApp->setOverrideCursor(QCursor(Qt::BlankCursor));
cursorHasBeenHidden = true;
}

Vector2i cp = ClientApp::windowSystem().latestMousePosition();
cursorX->set(cp.x);
cursorY->set(cp.y);
}
else
{
cursorHasBeenHidden = false;
}
}
};

ClientWindow::ClientWindow(String const &id)
Expand Down Expand Up @@ -679,6 +727,9 @@ void ClientWindow::canvasGLDraw(Canvas &canvas)
DENG_ASSERT_IN_MAIN_THREAD();
DENG_ASSERT_GL_CONTEXT_ACTIVE();

// Cursor position (if cursor is visible).
d->updateMouseCursor();

if(d->needRootSizeUpdate)
{
d->updateRootSize();
Expand Down Expand Up @@ -873,6 +924,9 @@ void ClientWindow::showColorAdjustments()
void ClientWindow::addOnTop(GuiWidget *widget)
{
d->container().add(widget);

// Make sure the cursor remains the topmost widget.
d->container().moveChildToLast(*d->cursor);
}

void ClientWindow::setSidebar(SidebarLocation location, GuiWidget *sidebar)
Expand Down
5 changes: 5 additions & 0 deletions doomsday/client/src/ui/windowsystem.cpp
Expand Up @@ -127,6 +127,11 @@ void WindowSystem::dispatchLatestMousePosition()
d->processLatestMousePosition();
}

Vector2i WindowSystem::latestMousePosition() const
{
return d->latestMousePos;
}

bool WindowSystem::processEvent(Event const &event)
{
/*
Expand Down
7 changes: 6 additions & 1 deletion doomsday/libdeng2/include/de/widgets/widget.h
Expand Up @@ -194,7 +194,9 @@ class DENG2_PUBLIC Widget

bool isEventRouted(int type, Widget *to) const;

// Tree organization.
/*
* Tree organization.
*/
void clearTree();

/**
Expand All @@ -213,9 +215,12 @@ class DENG2_PUBLIC Widget
Widget *find(String const &name);
Widget const *find(String const &name) const;
void moveChildBefore(Widget *child, Widget const &otherChild);
void moveChildToLast(Widget &child);
Children children() const;
dsize childCount() const;
Widget *parent() const;
bool isFirstChild() const;
bool isLastChild() const;

// Utilities.
String uniqueName(String const &name) const;
Expand Down
22 changes: 22 additions & 0 deletions doomsday/libdeng2/src/widgets/widget.cpp
Expand Up @@ -356,11 +356,33 @@ void Widget::moveChildBefore(Widget *child, Widget const &otherChild)
d->children.insert(to, child);
}

void Widget::moveChildToLast(Widget &child)
{
DENG2_ASSERT(child->parent() == this);
if(!child.isLastChild())
{
remove(child);
add(&child);
}
}

Widget *Widget::parent() const
{
return d->parent;
}

bool Widget::isFirstChild() const
{
if(!parent()) return false;
return this == parent()->d->children.first();
}

bool Widget::isLastChild() const
{
if(!parent()) return false;
return this == parent()->d->children.last();
}

String Widget::uniqueName(String const &name) const
{
return String("#%1.%2").arg(id().asInt64()).arg(name);
Expand Down

0 comments on commit 4c3aeac

Please sign in to comment.