Initial commit

This commit is contained in:
Niklas080208
2026-02-11 20:33:01 +01:00
commit 617265f004
145 changed files with 45252 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
current_dir := $(BOREALIS_PATH)/$(notdir $(patsubst %/,%,$(dir $(mkfile_path))))
LIBS := -lglfw3 -lEGL -lglapi -ldrm_nouveau -lm $(LIBS)
SOURCES := $(SOURCES) \
$(current_dir)/lib \
$(current_dir)/lib/extern/glad \
$(current_dir)/lib/extern/nanovg \
$(current_dir)/lib/extern/libretro-common/compat \
$(current_dir)/lib/extern/libretro-common/encodings \
$(current_dir)/lib/extern/libretro-common/features
INCLUDES := $(INCLUDES) \
$(current_dir)/include \
$(current_dir)/include/borealis/extern/glad \
$(current_dir)/include/borealis/extern/nanovg \
$(current_dir)/include/borealis/extern/libretro-common

View File

@@ -0,0 +1,56 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef BOREALIS_RESOURCES
#error BOREALIS_RESOURCES define missing
#endif
#define BOREALIS_ASSET(_str) BOREALIS_RESOURCES _str
// Library
#include <borealis/applet_frame.hpp>
#include <borealis/application.hpp>
#include <borealis/box_layout.hpp>
#include <borealis/button.hpp>
#include <borealis/crash_frame.hpp>
#include <borealis/dialog.hpp>
#include <borealis/dropdown.hpp>
#include <borealis/event.hpp>
#include <borealis/header.hpp>
#include <borealis/image.hpp>
#include <borealis/label.hpp>
#include <borealis/layer_view.hpp>
#include <borealis/list.hpp>
#include <borealis/logger.hpp>
#include <borealis/material_icon.hpp>
#include <borealis/notification_manager.hpp>
#include <borealis/popup_frame.hpp>
#include <borealis/progress_display.hpp>
#include <borealis/progress_spinner.hpp>
#include <borealis/rectangle.hpp>
#include <borealis/repeating_task.hpp>
#include <borealis/sidebar.hpp>
#include <borealis/staged_applet_frame.hpp>
#include <borealis/style.hpp>
#include <borealis/tab_frame.hpp>
#include <borealis/table.hpp>
#include <borealis/theme.hpp>
#include <borealis/thumbnail_frame.hpp>
#include <borealis/view.hpp>

View File

@@ -0,0 +1,68 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2020 WerWolv
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <string>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
namespace brls
{
class View;
typedef std::function<bool(void)> ActionListener;
// ZL and ZR do not exist here because GLFW doesn't know them
enum class Key
{
A = GLFW_GAMEPAD_BUTTON_A,
B = GLFW_GAMEPAD_BUTTON_B,
X = GLFW_GAMEPAD_BUTTON_X,
Y = GLFW_GAMEPAD_BUTTON_Y,
LSTICK = GLFW_GAMEPAD_BUTTON_LEFT_THUMB,
RSTICK = GLFW_GAMEPAD_BUTTON_RIGHT_THUMB,
L = GLFW_GAMEPAD_BUTTON_LEFT_BUMPER,
R = GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER,
PLUS = GLFW_GAMEPAD_BUTTON_START,
MINUS = GLFW_GAMEPAD_BUTTON_BACK,
DLEFT = GLFW_GAMEPAD_BUTTON_DPAD_LEFT,
DUP = GLFW_GAMEPAD_BUTTON_DPAD_UP,
DRIGHT = GLFW_GAMEPAD_BUTTON_DPAD_RIGHT,
DDOWN = GLFW_GAMEPAD_BUTTON_DPAD_DOWN,
};
struct Action
{
Key key;
std::string hintText;
bool available;
bool hidden;
ActionListener actionListener;
bool operator==(const Key other)
{
return this->key == other;
}
};
} // namespace brls

View File

@@ -0,0 +1,178 @@
/* RetroArch - A frontend for libretro.
* Borealis, a Nintendo Switch UI Library
* Copyright (C) 2014-2017 - Jean-André Santoni
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2019 - natinusala
Copyright (C) 2019 - p-sam
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <functional>
namespace brls
{
typedef float (*easing_cb)(float, float, float, float);
typedef std::function<void(void*)> tween_cb;
enum menu_animation_ctl_state
{
MENU_ANIMATION_CTL_NONE = 0,
MENU_ANIMATION_CTL_DEINIT,
MENU_ANIMATION_CTL_CLEAR_ACTIVE,
MENU_ANIMATION_CTL_SET_ACTIVE
};
enum menu_animation_easing_type
{
/* Linear */
EASING_LINEAR = 0,
/* Quad */
EASING_IN_QUAD,
EASING_OUT_QUAD,
EASING_IN_OUT_QUAD,
EASING_OUT_IN_QUAD,
/* Cubic */
EASING_IN_CUBIC,
EASING_OUT_CUBIC,
EASING_IN_OUT_CUBIC,
EASING_OUT_IN_CUBIC,
/* Quart */
EASING_IN_QUART,
EASING_OUT_QUART,
EASING_IN_OUT_QUART,
EASING_OUT_IN_QUART,
/* Quint */
EASING_IN_QUINT,
EASING_OUT_QUINT,
EASING_IN_OUT_QUINT,
EASING_OUT_IN_QUINT,
/* Sine */
EASING_IN_SINE,
EASING_OUT_SINE,
EASING_IN_OUT_SINE,
EASING_OUT_IN_SINE,
/* Expo */
EASING_IN_EXPO,
EASING_OUT_EXPO,
EASING_IN_OUT_EXPO,
EASING_OUT_IN_EXPO,
/* Circ */
EASING_IN_CIRC,
EASING_OUT_CIRC,
EASING_IN_OUT_CIRC,
EASING_OUT_IN_CIRC,
/* Bounce */
EASING_IN_BOUNCE,
EASING_OUT_BOUNCE,
EASING_IN_OUT_BOUNCE,
EASING_OUT_IN_BOUNCE,
EASING_LAST
};
/* TODO:
* Add a reverse loop ticker for languages
* that read right to left */
enum menu_animation_ticker_type
{
TICKER_TYPE_BOUNCE = 0,
TICKER_TYPE_LOOP,
TICKER_TYPE_LAST
};
typedef uintptr_t menu_animation_ctx_tag;
typedef struct menu_animation_ctx_subject
{
size_t count;
const void* data;
} menu_animation_ctx_subject_t;
typedef struct menu_animation_ctx_entry
{
enum menu_animation_easing_type easing_enum;
uintptr_t tag;
float duration;
float target_value;
float* subject;
tween_cb cb;
tween_cb tick;
void* userdata;
} menu_animation_ctx_entry_t;
typedef struct menu_animation_ctx_ticker
{
bool selected;
size_t len;
uint64_t idx;
enum menu_animation_ticker_type type_enum;
char* s;
const char* str;
const char* spacer;
} menu_animation_ctx_ticker_t;
typedef float menu_timer_t;
typedef struct menu_timer_ctx_entry
{
float duration;
tween_cb cb;
tween_cb tick;
void* userdata;
} menu_timer_ctx_entry_t;
typedef struct menu_delayed_animation
{
menu_timer_t timer;
menu_animation_ctx_entry_t entry;
} menu_delayed_animation_t;
void menu_timer_start(menu_timer_t* timer, menu_timer_ctx_entry_t* timer_entry);
void menu_timer_kill(menu_timer_t* timer);
void menu_animation_init(void);
void menu_animation_free(void);
bool menu_animation_update(void);
bool menu_animation_ticker(menu_animation_ctx_ticker_t* ticker);
float menu_animation_get_delta_time(void);
bool menu_animation_is_active(void);
bool menu_animation_kill_by_tag(menu_animation_ctx_tag* tag);
void menu_animation_kill_by_subject(menu_animation_ctx_subject_t* subject);
bool menu_animation_push(menu_animation_ctx_entry_t* entry);
void menu_animation_push_delayed(unsigned delay, menu_animation_ctx_entry_t* entry);
bool menu_animation_ctl(enum menu_animation_ctl_state state, void* data);
uint64_t menu_animation_get_ticker_idx(void);
uint64_t menu_animation_get_ticker_slow_idx(void);
void menu_animation_get_highlight(float* gradient_x, float* gradient_y, float* color);
} // namespace brls

View File

@@ -0,0 +1,94 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/frame_context.hpp>
#include <borealis/hint.hpp>
#include <borealis/image.hpp>
#include <borealis/view.hpp>
#include <string>
namespace brls
{
enum class HeaderStyle
{
REGULAR,
POPUP // Only meant for PopupFrames. Using it in other contexts might cause weird behaviour
};
// A Horizon settings-like frame, with header and footer (no sidebar)
class AppletFrame : public View
{
private:
std::string title = "";
std::string footerText = "";
std::string subTitleLeft = "", subTitleRight = "";
View* icon = nullptr;
Hint* hint = nullptr;
View* contentView = nullptr;
bool slideOut = false;
bool slideIn = false;
ViewAnimation animation;
protected:
HeaderStyle headerStyle = HeaderStyle::REGULAR;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
unsigned leftPadding = 0;
unsigned rightPadding = 0;
public:
AppletFrame(bool padLeft, bool padRight);
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
View* getDefaultFocus() override;
virtual bool onCancel();
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
void show(std::function<void(void)> cb, bool animate = true, ViewAnimation animation = ViewAnimation::FADE) override;
void hide(std::function<void(void)> cb, bool animated = true, ViewAnimation animation = ViewAnimation::FADE) override;
void onWindowSizeChanged() override;
void setTitle(std::string title);
void setFooterText(std::string footerText);
void setSubtitle(std::string left, std::string right);
void setIcon(unsigned char* buffer, size_t bufferSize);
void setIcon(std::string imagePath);
void setIcon(View* view);
virtual void setContentView(View* view);
bool hasContentView();
void setHeaderStyle(HeaderStyle headerStyle);
void setAnimateHint(bool animate)
{
this->hint->setAnimate(animate);
}
~AppletFrame();
};
} // namespace brls

View File

@@ -0,0 +1,204 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <nanovg.h>
#include <borealis/animations.hpp>
#include <borealis/frame_context.hpp>
#include <borealis/hint.hpp>
#include <borealis/label.hpp>
#include <borealis/logger.hpp>
#include <borealis/notification_manager.hpp>
#include <borealis/style.hpp>
#include <borealis/task_manager.hpp>
#include <borealis/theme.hpp>
#include <borealis/view.hpp>
#include <map>
#include <vector>
namespace brls
{
// The top-right framerate counter
class FramerateCounter : public Label
{
private:
retro_time_t lastSecond = 0;
unsigned frames = 0;
public:
FramerateCounter();
void frame(FrameContext* ctx) override;
};
class Application
{
public:
//Init with default style and theme (as close to HOS as possible)
static bool init(std::string title);
// Init with given style and theme
static bool init(std::string title, Style style, Theme theme);
static bool mainLoop();
/**
* Pushes a view on this applications's view stack
*
* The view will automatically be resized to take
* the whole screen and layout() will be called
*
* The view will gain focus if applicable
*/
static void pushView(View* view, ViewAnimation animation = ViewAnimation::FADE);
/**
* Pops the last pushed view from the stack
* and gives focus back where it was before
*/
static void popView(
ViewAnimation animation = ViewAnimation::FADE, std::function<void(void)> cb = []() {});
/**
* Gives the focus to the given view
* or clears the focus if given nullptr
*/
static void giveFocus(View* view);
static Style* getStyle();
static void setTheme(Theme theme);
static ThemeValues* getThemeValues();
static ThemeValues* getThemeValuesForVariant(ThemeVariant variant);
static ThemeVariant getThemeVariant();
static int loadFont(const char* fontName, const char* filePath);
static int loadFontFromMemory(const char* fontName, void* data, size_t size, bool freeData);
static int findFont(const char* fontName);
static FontStash* getFontStash();
static void notify(std::string text);
static void onGamepadButtonPressed(char button, bool repeating);
/**
* "Crashes" the app (displays a fullscreen CrashFrame)
*/
static void crash(std::string text);
static void quit();
/**
* Blocks any and all user inputs
*/
static void blockInputs();
/**
* Unblocks inputs after a call to
* blockInputs()
*/
static void unblockInputs();
static NVGcontext* getNVGContext();
static TaskManager* getTaskManager();
static NotificationManager* getNotificationManager();
static void setCommonFooter(std::string footer);
static std::string* getCommonFooter();
static void setDisplayFramerate(bool enabled);
static void toggleFramerateDisplay();
static void setMaximumFPS(unsigned fps);
// public so that the glfw callback can access it
inline static unsigned contentWidth, contentHeight;
inline static float windowScale;
static void resizeFramerateCounter();
static void resizeNotificationManager();
static GenericEvent* getGlobalFocusChangeEvent();
static VoidEvent* getGlobalHintsUpdateEvent();
static View* getCurrentFocus();
static std::string getTitle();
private:
inline static GLFWwindow* window;
inline static NVGcontext* vg;
inline static std::string title;
inline static TaskManager* taskManager;
inline static NotificationManager* notificationManager;
inline static FontStash fontStash;
inline static std::vector<View*> viewStack;
inline static std::vector<View*> focusStack;
inline static unsigned windowWidth, windowHeight;
inline static View* currentFocus;
inline static Theme currentTheme;
inline static ThemeVariant currentThemeVariant;
inline static GLFWgamepadstate oldGamepad;
inline static GLFWgamepadstate gamepad;
inline static Style currentStyle;
inline static unsigned blockInputsTokens = 0; // any value > 0 means inputs are blocked
inline static std::string commonFooter = "";
inline static FramerateCounter* framerateCounter = nullptr;
inline static float frameTime = 0.0f;
inline static View* repetitionOldFocus = nullptr;
inline static GenericEvent globalFocusChangeEvent;
inline static VoidEvent globalHintsUpdateEvent;
static void navigate(FocusDirection direction);
static void onWindowSizeChanged();
static void frame();
static void clear();
static void exit();
/**
* Handles actions for the currently focused view and
* the given button
* Returns true if at least one action has been fired
*/
static bool handleAction(char button);
};
} // namespace brls

View File

@@ -0,0 +1,173 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
#include <vector>
namespace brls
{
enum class BoxLayoutOrientation
{
VERTICAL,
HORIZONTAL
};
// TODO: Implement all gravity options for both orientations
enum class BoxLayoutGravity
{
DEFAULT, // left for horizontal, top for vertical
LEFT,
RIGHT,
TOP,
BOTTOM,
CENTER
};
class BoxLayoutChild
{
public:
View* view;
bool fill; // should the child fill the remaining space?
};
// A basic horizontal or vertical box layout :
// - Children can currently only be stretched to full width (vertical) or height (horizontal)
// - Only works with children with fixed width (horizontal) or height (vertical)
// TODO: More complex alignment and/or stretching parameters to children
class BoxLayout : public View
{
private:
BoxLayoutOrientation orientation;
unsigned spacing = 0;
bool resize = false; // should the view be resized according to children size after a layout?
BoxLayoutGravity gravity = BoxLayoutGravity::DEFAULT;
protected:
std::vector<BoxLayoutChild*> children;
size_t originalDefaultFocus = 0;
size_t defaultFocusedIndex = 0;
bool childFocused = false;
bool rememberFocus = false;
unsigned marginTop = 0;
unsigned marginRight = 0;
unsigned marginBottom = 0;
unsigned marginLeft = 0;
/**
* Should the BoxLayout apply spacing after
* this view?
*/
virtual void customSpacing(View* current, View* next, int* spacing) {}
public:
BoxLayout(BoxLayoutOrientation orientation, size_t defaultFocus = 0);
~BoxLayout();
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
View* getNextFocus(FocusDirection direction, void* parentUserdata) override;
View* getDefaultFocus() override;
void onChildFocusGained(View* child) override;
void onChildFocusLost(View* child) override;
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
void onWindowSizeChanged() override;
/**
* Sets gravity
*/
void setGravity(BoxLayoutGravity gravity);
/**
* Sets spacing between views
*/
void setSpacing(unsigned spacing);
unsigned getSpacing();
/**
* Sets margins around views
* Bottom (vertical) or right (horizontal) are
* only effective if the last child is set to fill
*/
void setMargins(unsigned top, unsigned right, unsigned bottom, unsigned left);
void setMarginBottom(unsigned bottom);
/**
* Adds a view to this box layout
* If fill is set to true, the child will
* fill the remaining space
*/
void addView(View* view, bool fill = false, bool resetState = false);
/**
* Removes the view at specified
* The view will be freed if free
* is set to true (defaults to true)
*
* Warning: this method isn't correctly
* implemented - currently removing a view will
* most likely result in memory corruption
*/
void removeView(int index, bool free = true);
/**
* Removes all views
* from this layout
*/
void clear(bool free = true);
/**
* Returns true if this layout
* doesn't contain any views
*/
bool isEmpty();
bool isChildFocused();
void setFocusedIndex(unsigned index);
size_t getViewsCount();
View* getChild(size_t i);
/**
* If enabled, will force the layout to resize itself
* to match the children size
* Mandatory for using in a ScrollView
*/
void setResize(bool resize);
/**
* Should the default focus be set to the originally focused
* view (until the layout disappears)?
*/
void setRememberFocus(bool rememberFocus);
};
} // namespace brls

View File

@@ -0,0 +1,99 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/image.hpp>
#include <borealis/label.hpp>
#include <borealis/view.hpp>
namespace brls
{
enum class ButtonStyle
{
PLAIN = 0, // regular, plain button
BORDERED, // text and a border
BORDERLESS, // only text
CRASH, // same as borderless but with a different text color
DIALOG // same as borderless but with a different text color
};
enum class ButtonState
{
ENABLED = 0,
DISABLED
};
// A button
class Button : public View
{
private:
ButtonStyle style;
Label* label = nullptr;
Image* image = nullptr;
GenericEvent clickEvent;
LabelStyle getLabelStyle();
ButtonState state = ButtonState::ENABLED;
float cornerRadiusOverride = 0;
public:
Button(ButtonStyle style);
~Button();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
virtual bool onClick();
void layout(NVGcontext* vg, Style* style, FontStash* stash);
void getHighlightInsets(unsigned* top, unsigned* right, unsigned* bottom, unsigned* left) override;
ButtonState getState();
void setState(ButtonState state);
Button* setLabel(std::string label);
Button* setImage(std::string path);
Button* setImage(unsigned char* buffer, size_t bufferSize);
GenericEvent* getClickEvent();
View* getDefaultFocus() override
{
return this;
}
void setCornerRadius(float cornerRadius);
void getHighlightMetrics(Style* style, float* cornerRadius) override
{
if (cornerRadiusOverride)
*cornerRadius = cornerRadiusOverride;
else
*cornerRadius = style->Button.cornerRadius;
}
bool isHighlightBackgroundEnabled() override
{
return false;
}
};
} // namespace brls

View File

@@ -0,0 +1,55 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/button.hpp>
#include <borealis/hint.hpp>
#include <borealis/label.hpp>
#include <borealis/view.hpp>
namespace brls
{
// A screen similar to the "The software has closed" dialog
// pressing OK will exit the app
class CrashFrame : public View
{
private:
Label* label;
Button* button;
Hint* hint;
public:
CrashFrame(std::string text);
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void onShowAnimationEnd() override;
View* getDefaultFocus() override;
bool isTranslucent() override
{
return true; // have it always translucent to disable fade out animation
}
~CrashFrame();
};
} // namespace brls

View File

@@ -0,0 +1,95 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/box_layout.hpp>
#include <borealis/view.hpp>
namespace brls
{
// TODO: Add a "can be cancelled with B" flag
// TODO: Add buttons at creation time
// TODO: Add the blurred dialog type once the blur is finished
class DialogButton
{
public:
std::string label;
GenericEvent::Callback cb;
};
// A modal dialog with zero to three buttons
// and anything as content
// Create the dialog then use open() and close()
class Dialog : public View
{
private:
View* contentView = nullptr;
unsigned frameX, frameY, frameWidth, frameHeight;
std::vector<DialogButton*> buttons;
BoxLayout* verticalButtonsLayout = nullptr;
BoxLayout* horizontalButtonsLayout = nullptr;
void rebuildButtons();
unsigned getButtonsHeight();
bool cancelable = true;
public:
Dialog(std::string text);
Dialog(View* contentView);
~Dialog();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
View* getDefaultFocus() override;
virtual bool onCancel();
/**
* Adds a button to this dialog, with a maximum of three
* The position depends on the add order
*
* Adding a button after the dialog has been opened is
* NOT SUPPORTED
*/
void addButton(std::string label, GenericEvent::Callback cb);
/**
* A cancelable dialog is closed when
* the user presses B (defaults to true)
*
* A dialog without any buttons cannot
* be cancelable
*/
void setCancelable(bool cancelable);
void open();
void close(std::function<void(void)> cb = []() {});
bool isTranslucent() override
{
return true;
}
};
} // namespace brls

View File

@@ -0,0 +1,80 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019-2020 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/event.hpp>
#include <borealis/list.hpp>
#include <borealis/view.hpp>
#include <string>
namespace brls
{
// Fired when the user has selected a value
//
// Parameter is either the selected value index
// or -1 if the user cancelled
//
// Assume that the Dropdown is deleted
// as soon as this function is called
typedef Event<int> ValueSelectedEvent;
// Allows the user to select between multiple
// values
// Use Dropdown::open()
class Dropdown : public View
{
private:
Dropdown(std::string title, std::vector<std::string> values, ValueSelectedEvent::Callback cb, size_t selected = 0);
std::string title;
int valuesCount;
ValueSelectedEvent valueEvent;
List* list;
Hint* hint;
float topOffset; // for slide in animation
protected:
unsigned getShowAnimationDuration(ViewAnimation animation) override;
public:
~Dropdown();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
View* getDefaultFocus() override;
virtual bool onCancel();
void show(std::function<void(void)> cb, bool animate = true, ViewAnimation animation = ViewAnimation::FADE) override;
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
static void open(std::string title, std::vector<std::string> values, ValueSelectedEvent::Callback cb, int selected = -1);
bool isTranslucent() override
{
return true || View::isTranslucent();
}
};
} // namespace brls

View File

@@ -0,0 +1,75 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <algorithm>
#include <functional>
#include <list>
namespace brls
{
// Simple observer pattern implementation
//
// Usage:
// 1. typedef your event type
// 2. create as many events as you want using that type
// 3. call subscribe on the events with your observers
// 4. call fire when you want to fire the events
// it wil return true if at least one subscriber exists
// for that event
template <typename... Ts>
class Event
{
public:
typedef std::function<void(Ts...)> Callback;
typedef std::list<Callback> CallbacksList;
typedef typename CallbacksList::iterator Subscription;
Subscription subscribe(Callback cb);
void unsubscribe(Subscription subscription);
bool fire(Ts... args);
private:
CallbacksList callbacks;
};
template <typename... Ts>
typename Event<Ts...>::Subscription Event<Ts...>::subscribe(Event<Ts...>::Callback cb)
{
this->callbacks.push_back(cb);
return --this->callbacks.end();
}
template <typename... Ts>
void Event<Ts...>::unsubscribe(Event<Ts...>::Subscription subscription)
{
this->callbacks.erase(subscription);
}
template <typename... Ts>
bool Event<Ts...>::fire(Ts... args)
{
for (Callback cb : this->callbacks)
cb(args...);
return !this->callbacks.empty();
}
}; // namespace brls

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (boolean.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_BOOLEAN_H
#define __LIBRETRO_SDK_BOOLEAN_H
#ifndef __cplusplus
#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(SN_TARGET_PS3)
/* Hack applied for MSVC when compiling in C89 mode as it isn't C99 compliant. */
#define bool unsigned char
#define true 1
#define false 0
#else
#include <stdbool.h>
#endif
#endif
#endif

View File

@@ -0,0 +1,59 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (strl.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_COMPAT_STRL_H
#define __LIBRETRO_SDK_COMPAT_STRL_H
#include <string.h>
#include <stddef.h>
#if defined(RARCH_INTERNAL) && defined(HAVE_CONFIG_H)
#include "../../../config.h"
#endif
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
#ifdef __MACH__
#ifndef HAVE_STRL
#define HAVE_STRL
#endif
#endif
#ifndef HAVE_STRL
/* Avoid possible naming collisions during link since
* we prefer to use the actual name. */
#define strlcpy(dst, src, size) strlcpy_retro__(dst, src, size)
#define strlcat(dst, src, size) strlcat_retro__(dst, src, size)
size_t strlcpy(char *dest, const char *source, size_t size);
size_t strlcat(char *dest, const char *source, size_t size);
#endif
char *strldup(const char *s, size_t n);
RETRO_END_DECLS
#endif

View File

@@ -0,0 +1,67 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (utf.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _LIBRETRO_ENCODINGS_UTF_H
#define _LIBRETRO_ENCODINGS_UTF_H
#include <stdint.h>
#include <stddef.h>
#include <boolean.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
enum CodePage
{
CODEPAGE_LOCAL = 0, /* CP_ACP */
CODEPAGE_UTF8 = 65001 /* CP_UTF8 */
};
size_t utf8_conv_utf32(uint32_t *out, size_t out_chars,
const char *in, size_t in_size);
bool utf16_conv_utf8(uint8_t *out, size_t *out_chars,
const uint16_t *in, size_t in_size);
size_t utf8len(const char *string);
size_t utf8cpy(char *d, size_t d_len, const char *s, size_t chars);
const char *utf8skip(const char *str, size_t chars);
uint32_t utf8_walk(const char **string);
bool utf16_to_char_string(const uint16_t *in, char *s, size_t len);
char* utf8_to_local_string_alloc(const char *str);
char* local_to_utf8_string_alloc(const char *str);
wchar_t* utf8_to_utf16_string_alloc(const char *str);
char* utf16_to_utf8_string_alloc(const wchar_t *str);
RETRO_END_DECLS
#endif

View File

@@ -0,0 +1,75 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (features_cpu.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _LIBRETRO_SDK_CPU_INFO_H
#define _LIBRETRO_SDK_CPU_INFO_H
#include <retro_common_api.h>
#include <stdint.h>
#include <libretro.h>
RETRO_BEGIN_DECLS
/**
* cpu_features_get_perf_counter:
*
* Gets performance counter.
*
* Returns: performance counter.
**/
retro_perf_tick_t cpu_features_get_perf_counter(void);
/**
* cpu_features_get_time_usec:
*
* Gets time in microseconds, from an undefined epoch.
* The epoch may change between computers or across reboots.
*
* Returns: time in microseconds
**/
retro_time_t cpu_features_get_time_usec(void);
/**
* cpu_features_get:
*
* Gets CPU features.
*
* Returns: bitmask of all CPU features available.
**/
uint64_t cpu_features_get(void);
/**
* cpu_features_get_core_amount:
*
* Gets the amount of available CPU cores.
*
* Returns: amount of CPU cores available.
**/
unsigned cpu_features_get_core_amount(void);
void cpu_features_get_model_name(char *name, int len);
RETRO_END_DECLS
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_assert.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __RETRO_ASSERT_H
#define __RETRO_ASSERT_H
#include <assert.h>
#ifdef RARCH_INTERNAL
#include <stdio.h>
#define retro_assert(cond) do { \
if (!(cond)) { printf("Assertion failed at %s:%d.\n", __FILE__, __LINE__); abort(); } \
} while(0)
#else
#define retro_assert(cond) assert(cond)
#endif
#endif

View File

@@ -0,0 +1,117 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_common_api.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _LIBRETRO_COMMON_RETRO_COMMON_API_H
#define _LIBRETRO_COMMON_RETRO_COMMON_API_H
/*
This file is designed to normalize the libretro-common compiling environment
for public API headers. This should be leaner than a normal compiling environment,
since it gets #included into other project's sources.
*/
/* ------------------------------------ */
/*
Ordinarily we want to put #ifdef __cplusplus extern "C" in C library
headers to enable them to get used by c++ sources.
However, we want to support building this library as C++ as well, so a
special technique is called for.
*/
#define RETRO_BEGIN_DECLS
#define RETRO_END_DECLS
#ifdef __cplusplus
#ifdef CXX_BUILD
/* build wants everything to be built as c++, so no extern "C" */
#else
#undef RETRO_BEGIN_DECLS
#undef RETRO_END_DECLS
#define RETRO_BEGIN_DECLS extern "C" {
#define RETRO_END_DECLS }
#endif
#else
/* header is included by a C source file, so no extern "C" */
#endif
/*
IMO, this non-standard ssize_t should not be used.
However, it's a good example of how to handle something like this.
*/
#ifdef _MSC_VER
#ifndef HAVE_SSIZE_T
#define HAVE_SSIZE_T
#if defined(_WIN64)
typedef __int64 ssize_t;
#elif defined(_WIN32)
typedef int ssize_t;
#endif
#endif
#elif defined(__MACH__)
#include <sys/types.h>
#endif
#ifdef _MSC_VER
#if _MSC_VER >= 1800
#include <inttypes.h>
#else
#ifndef PRId64
#define PRId64 "I64d"
#define PRIu64 "I64u"
#define PRIuPTR "Iu"
#endif
#endif
#else
/* C++11 says this one isn't needed, but apparently (some versions of) mingw require it anyways */
/* https://stackoverflow.com/questions/8132399/how-to-printf-uint64-t-fails-with-spurious-trailing-in-format */
/* https://github.com/libretro/RetroArch/issues/6009 */
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#endif
#ifndef PRId64
#error "inttypes.h is being screwy"
#endif
#define STRING_REP_INT64 "%" PRId64
#define STRING_REP_UINT64 "%" PRIu64
#define STRING_REP_USIZE "%" PRIuPTR
/*
I would like to see retro_inline.h moved in here; possibly boolean too.
rationale: these are used in public APIs, and it is easier to find problems
and write code that works the first time portably when theyre included uniformly
than to do the analysis from scratch each time you think you need it, for each feature.
Moreover it helps force you to make hard decisions: if you EVER bring in boolean.h,
then you should pay the price everywhere, so you can see how much grief it will cause.
Of course, another school of thought is that you should do as little damage as possible
in as few places as possible...
*/
/* _LIBRETRO_COMMON_RETRO_COMMON_API_H */
#endif

View File

@@ -0,0 +1,39 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_inline.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_INLINE_H
#define __LIBRETRO_SDK_INLINE_H
#ifndef INLINE
#if defined(_WIN32) || defined(__INTEL_COMPILER)
#define INLINE __inline
#elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
#define INLINE inline
#elif defined(__GNUC__)
#define INLINE __inline__
#else
#define INLINE
#endif
#endif
#endif

View File

@@ -0,0 +1,94 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_math.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _LIBRETRO_COMMON_MATH_H
#define _LIBRETRO_COMMON_MATH_H
#include <stdint.h>
#if defined(_WIN32) && !defined(_XBOX)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#elif defined(_WIN32) && defined(_XBOX)
#include <Xtl.h>
#endif
#include <limits.h>
#ifdef _MSC_VER
#include <compat/msvc.h>
#endif
#include <retro_inline.h>
#ifndef M_PI
#if !defined(USE_MATH_DEFINES)
#define M_PI 3.14159265358979323846264338327
#endif
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/**
* next_pow2:
* @v : initial value
*
* Get next power of 2 value based on initial value.
*
* Returns: next power of 2 value (derived from @v).
**/
static INLINE uint32_t next_pow2(uint32_t v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
/**
* prev_pow2:
* @v : initial value
*
* Get previous power of 2 value based on initial value.
*
* Returns: previous power of 2 value (derived from @v).
**/
static INLINE uint32_t prev_pow2(uint32_t v)
{
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
return v - (v >> 1);
}
#endif

View File

@@ -0,0 +1,182 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_miscellaneous.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __RARCH_MISCELLANEOUS_H
#define __RARCH_MISCELLANEOUS_H
#define RARCH_MAX_SUBSYSTEMS 10
#define RARCH_MAX_SUBSYSTEM_ROMS 10
#include <stdint.h>
#include <boolean.h>
#include <retro_inline.h>
#if defined(_WIN32) && !defined(_XBOX)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#elif defined(_WIN32) && defined(_XBOX)
#include <Xtl.h>
#endif
#if defined(__CELLOS_LV2__)
#include <sys/fs_external.h>
#endif
#include <limits.h>
#ifdef _MSC_VER
#include <compat/msvc.h>
#endif
static INLINE void bits_or_bits(uint32_t *a, uint32_t *b, uint32_t count)
{
uint32_t i;
for (i = 0; i < count;i++)
a[i] |= b[i];
}
static INLINE void bits_clear_bits(uint32_t *a, uint32_t *b, uint32_t count)
{
uint32_t i;
for (i = 0; i < count;i++)
a[i] &= ~b[i];
}
static INLINE bool bits_any_set(uint32_t* ptr, uint32_t count)
{
uint32_t i;
for (i = 0; i < count; i++)
{
if (ptr[i] != 0)
return true;
}
return false;
}
#ifndef PATH_MAX_LENGTH
#if defined(__CELLOS_LV2__)
#define PATH_MAX_LENGTH CELL_FS_MAX_FS_PATH_LENGTH
#elif defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(PS2) || defined(GEKKO)|| defined(WIIU) || defined(ORBIS)
#define PATH_MAX_LENGTH 512
#else
#define PATH_MAX_LENGTH 4096
#endif
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#define BITS_GET_ELEM(a, i) ((a).data[i])
#define BITS_GET_ELEM_PTR(a, i) ((a)->data[i])
#define BIT_SET(a, bit) ((a)[(bit) >> 3] |= (1 << ((bit) & 7)))
#define BIT_CLEAR(a, bit) ((a)[(bit) >> 3] &= ~(1 << ((bit) & 7)))
#define BIT_GET(a, bit) (((a)[(bit) >> 3] >> ((bit) & 7)) & 1)
#define BIT16_SET(a, bit) ((a) |= (1 << ((bit) & 15)))
#define BIT16_CLEAR(a, bit) ((a) &= ~(1 << ((bit) & 15)))
#define BIT16_GET(a, bit) (((a) >> ((bit) & 15)) & 1)
#define BIT16_CLEAR_ALL(a) ((a) = 0)
#define BIT32_SET(a, bit) ((a) |= (1 << ((bit) & 31)))
#define BIT32_CLEAR(a, bit) ((a) &= ~(1 << ((bit) & 31)))
#define BIT32_GET(a, bit) (((a) >> ((bit) & 31)) & 1)
#define BIT32_CLEAR_ALL(a) ((a) = 0)
#define BIT64_SET(a, bit) ((a) |= (UINT64_C(1) << ((bit) & 63)))
#define BIT64_CLEAR(a, bit) ((a) &= ~(UINT64_C(1) << ((bit) & 63)))
#define BIT64_GET(a, bit) (((a) >> ((bit) & 63)) & 1)
#define BIT64_CLEAR_ALL(a) ((a) = 0)
#define BIT128_SET(a, bit) ((a).data[(bit) >> 5] |= (1 << ((bit) & 31)))
#define BIT128_CLEAR(a, bit) ((a).data[(bit) >> 5] &= ~(1 << ((bit) & 31)))
#define BIT128_GET(a, bit) (((a).data[(bit) >> 5] >> ((bit) & 31)) & 1)
#define BIT128_CLEAR_ALL(a) memset(&(a), 0, sizeof(a))
#define BIT128_SET_PTR(a, bit) BIT128_SET(*a, bit)
#define BIT128_CLEAR_PTR(a, bit) BIT128_CLEAR(*a, bit)
#define BIT128_GET_PTR(a, bit) BIT128_GET(*a, bit)
#define BIT128_CLEAR_ALL_PTR(a) BIT128_CLEAR_ALL(*a)
#define BIT256_SET(a, bit) BIT128_SET(a, bit)
#define BIT256_CLEAR(a, bit) BIT128_CLEAR(a, bit)
#define BIT256_GET(a, bit) BIT128_GET(a, bit)
#define BIT256_CLEAR_ALL(a) BIT128_CLEAR_ALL(a)
#define BIT256_SET_PTR(a, bit) BIT256_SET(*a, bit)
#define BIT256_CLEAR_PTR(a, bit) BIT256_CLEAR(*a, bit)
#define BIT256_GET_PTR(a, bit) BIT256_GET(*a, bit)
#define BIT256_CLEAR_ALL_PTR(a) BIT256_CLEAR_ALL(*a)
#define BITS_COPY16_PTR(a,bits) \
{ \
BIT128_CLEAR_ALL_PTR(a); \
BITS_GET_ELEM_PTR(a, 0) = (bits) & 0xffff; \
}
#define BITS_COPY32_PTR(a,bits) \
{ \
BIT128_CLEAR_ALL_PTR(a); \
BITS_GET_ELEM_PTR(a, 0) = (bits); \
}
/* Helper macros and struct to keep track of many booleans. */
/* This struct has 256 bits. */
typedef struct
{
uint32_t data[8];
} retro_bits_t;
#ifdef _WIN32
# ifdef _WIN64
# define PRI_SIZET PRIu64
# else
# if _MSC_VER == 1800
# define PRI_SIZET PRIu32
# else
# define PRI_SIZET "u"
# endif
# endif
#elif PS2
# define PRI_SIZET "u"
#else
# if (SIZE_MAX == 0xFFFF)
# define PRI_SIZET "hu"
# elif (SIZE_MAX == 0xFFFFFFFF)
# define PRI_SIZET "u"
# elif (SIZE_MAX == 0xFFFFFFFFFFFFFFFF)
# define PRI_SIZET "lu"
# else
# error PRI_SIZET: unknown SIZE_MAX
# endif
#endif
#endif

View File

@@ -0,0 +1,116 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_timers.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_COMMON_TIMERS_H
#define __LIBRETRO_COMMON_TIMERS_H
#include <stdint.h>
#if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
#include <sys/timer.h>
#elif defined(XENON)
#include <time/time.h>
#elif defined(GEKKO) || defined(__PSL1GHT__) || defined(__QNX__)
#include <unistd.h>
#elif defined(WIIU)
#include <wiiu/os/thread.h>
#elif defined(PSP)
#include <pspthreadman.h>
#elif defined(VITA)
#include <psp2/kernel/threadmgr.h>
#elif defined(PS2)
#include <SDL/SDL_timer.h>
#elif defined(_3DS)
#include <3ds.h>
#else
#include <time.h>
#endif
#if defined(_WIN32) && !defined(_XBOX)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#elif defined(_WIN32) && defined(_XBOX)
#include <Xtl.h>
#endif
#include <limits.h>
#ifdef _MSC_VER
#include <compat/msvc.h>
#endif
#include <retro_inline.h>
#ifdef DJGPP
#define timespec timeval
#define tv_nsec tv_usec
#include <unistd.h>
extern int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
static int nanosleepDOS(const struct timespec *rqtp, struct timespec *rmtp)
{
usleep(1000000 * rqtp->tv_sec + rqtp->tv_nsec / 1000);
if (rmtp)
rmtp->tv_sec = rmtp->tv_nsec=0;
return 0;
}
#define nanosleep nanosleepDOS
#endif
/**
* retro_sleep:
* @msec : amount in milliseconds to sleep
*
* Sleeps for a specified amount of milliseconds (@msec).
**/
static INLINE void retro_sleep(unsigned msec)
{
#if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)
sys_timer_usleep(1000 * msec);
#elif defined(PSP) || defined(VITA)
sceKernelDelayThread(1000 * msec);
#elif defined(PS2)
SDL_Delay(msec);
#elif defined(_3DS)
svcSleepThread(1000000 * (s64)msec);
#elif defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
SleepEx(msec, FALSE);
#elif defined(_WIN32)
Sleep(msec);
#elif defined(XENON)
udelay(1000 * msec);
#elif defined(GEKKO) || defined(__PSL1GHT__) || defined(__QNX__)
usleep(1000 * msec);
#elif defined(WIIU)
OSSleepTicks(ms_to_ticks(msec));
#else
struct timespec tv = {0};
tv.tv_sec = msec / 1000;
tv.tv_nsec = (msec % 1000) * 1000000;
nanosleep(&tv, NULL);
#endif
}
#endif

View File

@@ -0,0 +1,111 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (file_stream.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_FILE_STREAM_H
#define __LIBRETRO_SDK_FILE_STREAM_H
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/types.h>
#include <libretro.h>
#include <retro_common_api.h>
#include <retro_inline.h>
#include <boolean.h>
#include <stdarg.h>
#define FILESTREAM_REQUIRED_VFS_VERSION 2
RETRO_BEGIN_DECLS
typedef struct RFILE RFILE;
#define FILESTREAM_REQUIRED_VFS_VERSION 2
void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info);
int64_t filestream_get_size(RFILE *stream);
int64_t filestream_truncate(RFILE *stream, int64_t length);
/**
* filestream_open:
* @path : path to file
* @mode : file mode to use when opening (read/write)
* @bufsize : optional buffer size (-1 or 0 to use default)
*
* Opens a file for reading or writing, depending on the requested mode.
* Returns a pointer to an RFILE if opened successfully, otherwise NULL.
**/
RFILE *filestream_open(const char *path, unsigned mode, unsigned hints);
int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position);
int64_t filestream_read(RFILE *stream, void *data, int64_t len);
int64_t filestream_write(RFILE *stream, const void *data, int64_t len);
int64_t filestream_tell(RFILE *stream);
void filestream_rewind(RFILE *stream);
int filestream_close(RFILE *stream);
int64_t filestream_read_file(const char *path, void **buf, int64_t *len);
char *filestream_gets(RFILE *stream, char *s, size_t len);
int filestream_getc(RFILE *stream);
int filestream_scanf(RFILE *stream, const char* format, ...);
int filestream_eof(RFILE *stream);
bool filestream_write_file(const char *path, const void *data, int64_t size);
int filestream_putc(RFILE *stream, int c);
int filestream_vprintf(RFILE *stream, const char* format, va_list args);
int filestream_printf(RFILE *stream, const char* format, ...);
int filestream_error(RFILE *stream);
int filestream_flush(RFILE *stream);
int filestream_delete(const char *path);
int filestream_rename(const char *old_path, const char *new_path);
const char *filestream_get_path(RFILE *stream);
bool filestream_exists(const char *path);
char *filestream_getline(RFILE *stream);
RETRO_END_DECLS
#endif

View File

@@ -0,0 +1,134 @@
/* Copyright (C) 2010-2019 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (stdstring.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STDSTRING_H
#define __LIBRETRO_SDK_STDSTRING_H
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#include <string.h>
#include <boolean.h>
#include <retro_common_api.h>
#include <retro_inline.h>
#include <compat/strl.h>
RETRO_BEGIN_DECLS
static INLINE bool string_is_empty(const char *data)
{
return !data || (*data == '\0');
}
static INLINE bool string_is_equal(const char *a, const char *b)
{
return (a && b) ? !strcmp(a, b) : false;
}
#define STRLEN_CONST(x) ((sizeof((x))-1))
#define string_is_not_equal(a, b) !string_is_equal((a), (b))
#define string_add_pair_open(s, size) strlcat((s), " (", (size))
#define string_add_pair_close(s, size) strlcat((s), ")", (size))
#define string_add_bracket_open(s, size) strlcat((s), "{", (size))
#define string_add_bracket_close(s, size) strlcat((s), "}", (size))
#define string_add_single_quote(s, size) strlcat((s), "'", (size))
#define string_add_quote(s, size) strlcat((s), "\"", (size))
#define string_add_colon(s, size) strlcat((s), ":", (size))
#define string_add_glob_open(s, size) strlcat((s), "glob('*", (size))
#define string_add_glob_close(s, size) strlcat((s), "*')", (size))
#define string_is_not_equal_fast(a, b, size) (memcmp(a, b, size) != 0)
#define string_is_equal_fast(a, b, size) (memcmp(a, b, size) == 0)
static INLINE void string_add_between_pairs(char *s, const char *str,
size_t size)
{
string_add_pair_open(s, size);
strlcat(s, str, size);
string_add_pair_close(s, size);
}
static INLINE bool string_is_equal_case_insensitive(const char *a,
const char *b)
{
int result = 0;
const unsigned char *p1 = (const unsigned char*)a;
const unsigned char *p2 = (const unsigned char*)b;
if (!a || !b)
return false;
if (p1 == p2)
return true;
while ((result = tolower (*p1) - tolower (*p2++)) == 0)
if (*p1++ == '\0')
break;
return (result == 0);
}
static INLINE bool string_is_equal_noncase(const char *a, const char *b)
{
int result = 0;
const unsigned char *p1 = (const unsigned char*)a;
const unsigned char *p2 = (const unsigned char*)b;
if (!a || !b)
return false;
if (p1 == p2)
return false;
while ((result = tolower (*p1) - tolower (*p2++)) == 0)
if (*p1++ == '\0')
break;
return (result == 0);
}
char *string_to_upper(char *s);
char *string_to_lower(char *s);
char *string_ucwords(char *s);
char *string_replace_substring(const char *in, const char *pattern,
const char *by);
/* Remove leading whitespaces */
char *string_trim_whitespace_left(char *const s);
/* Remove trailing whitespaces */
char *string_trim_whitespace_right(char *const s);
/* Remove leading and trailing whitespaces */
char *string_trim_whitespace(char *const s);
/* max_lines == 0 means no limit */
char *word_wrap(char *buffer, const char *string,
int line_width, bool unicode, unsigned max_lines);
RETRO_END_DECLS
#endif

View File

@@ -0,0 +1,18 @@
Copyright (c) 2013 Mikko Mononen memon@inside.org
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,685 @@
//
// Copyright (c) 2013 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#ifndef NANOVG_H
#define NANOVG_H
#ifdef __cplusplus
extern "C" {
#endif
#define NVG_PI 3.14159265358979323846264338327f
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union
#endif
typedef struct NVGcontext NVGcontext;
struct NVGcolor {
union {
float rgba[4];
struct {
float r,g,b,a;
};
};
};
typedef struct NVGcolor NVGcolor;
struct NVGpaint {
float xform[6];
float extent[2];
float radius;
float feather;
NVGcolor innerColor;
NVGcolor outerColor;
int image;
};
typedef struct NVGpaint NVGpaint;
enum NVGwinding {
NVG_CCW = 1, // Winding for solid shapes
NVG_CW = 2, // Winding for holes
};
enum NVGsolidity {
NVG_SOLID = 1, // CCW
NVG_HOLE = 2, // CW
};
enum NVGlineCap {
NVG_BUTT,
NVG_ROUND,
NVG_SQUARE,
NVG_BEVEL,
NVG_MITER,
};
enum NVGalign {
// Horizontal align
NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left.
NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center.
NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right.
// Vertical align
NVG_ALIGN_TOP = 1<<3, // Align text vertically to top.
NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle.
NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom.
NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline.
};
enum NVGblendFactor {
NVG_ZERO = 1<<0,
NVG_ONE = 1<<1,
NVG_SRC_COLOR = 1<<2,
NVG_ONE_MINUS_SRC_COLOR = 1<<3,
NVG_DST_COLOR = 1<<4,
NVG_ONE_MINUS_DST_COLOR = 1<<5,
NVG_SRC_ALPHA = 1<<6,
NVG_ONE_MINUS_SRC_ALPHA = 1<<7,
NVG_DST_ALPHA = 1<<8,
NVG_ONE_MINUS_DST_ALPHA = 1<<9,
NVG_SRC_ALPHA_SATURATE = 1<<10,
};
enum NVGcompositeOperation {
NVG_SOURCE_OVER,
NVG_SOURCE_IN,
NVG_SOURCE_OUT,
NVG_ATOP,
NVG_DESTINATION_OVER,
NVG_DESTINATION_IN,
NVG_DESTINATION_OUT,
NVG_DESTINATION_ATOP,
NVG_LIGHTER,
NVG_COPY,
NVG_XOR,
};
struct NVGcompositeOperationState {
int srcRGB;
int dstRGB;
int srcAlpha;
int dstAlpha;
};
typedef struct NVGcompositeOperationState NVGcompositeOperationState;
struct NVGglyphPosition {
const char* str; // Position of the glyph in the input string.
float x; // The x-coordinate of the logical glyph position.
float minx, maxx; // The bounds of the glyph shape.
};
typedef struct NVGglyphPosition NVGglyphPosition;
struct NVGtextRow {
const char* start; // Pointer to the input text where the row starts.
const char* end; // Pointer to the input text where the row ends (one past the last character).
const char* next; // Pointer to the beginning of the next row.
float width; // Logical width of the row.
float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
};
typedef struct NVGtextRow NVGtextRow;
enum NVGimageFlags {
NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image.
NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction.
NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction.
NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered.
NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha.
NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear
};
// Begin drawing a new frame
// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame()
// nvgBeginFrame() defines the size of the window to render to in relation currently
// set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
// control the rendering on Hi-DPI devices.
// For example, GLFW returns two dimension for an opened window: window size and
// frame buffer size. In that case you would set windowWidth/Height to the window size
// devicePixelRatio to: frameBufferWidth / windowWidth.
void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio);
// Cancels drawing the current frame.
void nvgCancelFrame(NVGcontext* ctx);
// Ends drawing flushing remaining render state.
void nvgEndFrame(NVGcontext* ctx);
//
// Composite operation
//
// The composite operations in NanoVG are modeled after HTML Canvas API, and
// the blend func is based on OpenGL (see corresponding manuals for more info).
// The colors in the blending state have premultiplied alpha.
// Sets the composite operation. The op parameter should be one of NVGcompositeOperation.
void nvgGlobalCompositeOperation(NVGcontext* ctx, int op);
// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor.
void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor);
// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor.
void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
//
// Color utils
//
// Colors in NanoVG are stored as unsigned ints in ABGR format.
// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b);
// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
NVGcolor nvgRGBf(float r, float g, float b);
// Returns a color value from red, green, blue and alpha values.
NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
// Returns a color value from red, green, blue and alpha values.
NVGcolor nvgRGBAf(float r, float g, float b, float a);
// Linearly interpolates from color c0 to c1, and returns resulting color value.
NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u);
// Sets transparency of a color value.
NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a);
// Sets transparency of a color value.
NVGcolor nvgTransRGBAf(NVGcolor c0, float a);
// Returns color value specified by hue, saturation and lightness.
// HSL values are all in range [0..1], alpha will be set to 255.
NVGcolor nvgHSL(float h, float s, float l);
// Returns color value specified by hue, saturation and lightness and alpha.
// HSL values are all in range [0..1], alpha in range [0..255]
NVGcolor nvgHSLA(float h, float s, float l, unsigned char a);
//
// State Handling
//
// NanoVG contains state which represents how paths will be rendered.
// The state contains transform, fill and stroke styles, text and font styles,
// and scissor clipping.
// Pushes and saves the current render state into a state stack.
// A matching nvgRestore() must be used to restore the state.
void nvgSave(NVGcontext* ctx);
// Pops and restores current render state.
void nvgRestore(NVGcontext* ctx);
// Resets current render state to default values. Does not affect the render state stack.
void nvgReset(NVGcontext* ctx);
//
// Render styles
//
// Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
// Solid color is simply defined as a color value, different kinds of paints can be created
// using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern().
//
// Current render style can be saved and restored using nvgSave() and nvgRestore().
// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default.
void nvgShapeAntiAlias(NVGcontext* ctx, int enabled);
// Sets current stroke style to a solid color.
void nvgStrokeColor(NVGcontext* ctx, NVGcolor color);
// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint);
// Sets current fill style to a solid color.
void nvgFillColor(NVGcontext* ctx, NVGcolor color);
// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
void nvgFillPaint(NVGcontext* ctx, NVGpaint paint);
// Sets the miter limit of the stroke style.
// Miter limit controls when a sharp corner is beveled.
void nvgMiterLimit(NVGcontext* ctx, float limit);
// Sets the stroke width of the stroke style.
void nvgStrokeWidth(NVGcontext* ctx, float size);
// Sets how the end of the line (cap) is drawn,
// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE.
void nvgLineCap(NVGcontext* ctx, int cap);
// Sets how sharp path corners are drawn.
// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL.
void nvgLineJoin(NVGcontext* ctx, int join);
// Sets the transparency applied to all rendered shapes.
// Already transparent paths will get proportionally more transparent as well.
void nvgGlobalAlpha(NVGcontext* ctx, float alpha);
//
// Transforms
//
// The paths, gradients, patterns and scissor region are transformed by an transformation
// matrix at the time when they are passed to the API.
// The current transformation matrix is a affine matrix:
// [sx kx tx]
// [ky sy ty]
// [ 0 0 1]
// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation.
// The last row is assumed to be 0,0,1 and is not stored.
//
// Apart from nvgResetTransform(), each transformation function first creates
// specific transformation matrix and pre-multiplies the current transformation by it.
//
// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore().
// Resets current transform to a identity matrix.
void nvgResetTransform(NVGcontext* ctx);
// Premultiplies current coordinate system by specified matrix.
// The parameters are interpreted as matrix as follows:
// [a c e]
// [b d f]
// [0 0 1]
void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f);
// Translates current coordinate system.
void nvgTranslate(NVGcontext* ctx, float x, float y);
// Rotates current coordinate system. Angle is specified in radians.
void nvgRotate(NVGcontext* ctx, float angle);
// Skews the current coordinate system along X axis. Angle is specified in radians.
void nvgSkewX(NVGcontext* ctx, float angle);
// Skews the current coordinate system along Y axis. Angle is specified in radians.
void nvgSkewY(NVGcontext* ctx, float angle);
// Scales the current coordinate system.
void nvgScale(NVGcontext* ctx, float x, float y);
// Stores the top part (a-f) of the current transformation matrix in to the specified buffer.
// [a c e]
// [b d f]
// [0 0 1]
// There should be space for 6 floats in the return buffer for the values a-f.
void nvgCurrentTransform(NVGcontext* ctx, float* xform);
// The following functions can be used to make calculations on 2x3 transformation matrices.
// A 2x3 matrix is represented as float[6].
// Sets the transform to identity matrix.
void nvgTransformIdentity(float* dst);
// Sets the transform to translation matrix matrix.
void nvgTransformTranslate(float* dst, float tx, float ty);
// Sets the transform to scale matrix.
void nvgTransformScale(float* dst, float sx, float sy);
// Sets the transform to rotate matrix. Angle is specified in radians.
void nvgTransformRotate(float* dst, float a);
// Sets the transform to skew-x matrix. Angle is specified in radians.
void nvgTransformSkewX(float* dst, float a);
// Sets the transform to skew-y matrix. Angle is specified in radians.
void nvgTransformSkewY(float* dst, float a);
// Sets the transform to the result of multiplication of two transforms, of A = A*B.
void nvgTransformMultiply(float* dst, const float* src);
// Sets the transform to the result of multiplication of two transforms, of A = B*A.
void nvgTransformPremultiply(float* dst, const float* src);
// Sets the destination to inverse of specified transform.
// Returns 1 if the inverse could be calculated, else 0.
int nvgTransformInverse(float* dst, const float* src);
// Transform a point by given transform.
void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy);
// Converts degrees to radians and vice versa.
float nvgDegToRad(float deg);
float nvgRadToDeg(float rad);
//
// Images
//
// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering.
// In addition you can upload your own image. The image loading is provided by stb_image.
// The parameter imageFlags is combination of flags defined in NVGimageFlags.
// Creates image by loading it from the disk from specified file name.
// Returns handle to the image.
int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags);
// Creates image by loading it from the specified chunk of memory.
// Returns handle to the image.
int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata);
// Creates image from specified image data.
// Returns handle to the image.
int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data);
// Updates image data specified by image handle.
void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data);
// Returns the dimensions of a created image.
void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h);
// Deletes created image.
void nvgDeleteImage(NVGcontext* ctx, int image);
//
// Paints
//
// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
// These can be used as paints for strokes and fills.
// Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates
// of the linear gradient, icol specifies the start color and ocol the end color.
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey,
NVGcolor icol, NVGcolor ocol);
// Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
// the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h,
float r, float f, NVGcolor icol, NVGcolor ocol);
// Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify
// the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr,
NVGcolor icol, NVGcolor ocol);
// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern,
// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render.
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey,
float angle, int image, float alpha);
//
// Scissoring
//
// Scissoring allows you to clip the rendering into a rectangle. This is useful for various
// user interface cases like rendering a text edit or a timeline.
// Sets the current scissor rectangle.
// The scissor rectangle is transformed by the current transform.
void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h);
// Intersects current scissor rectangle with the specified rectangle.
// The scissor rectangle is transformed by the current transform.
// Note: in case the rotation of previous scissor rect differs from
// the current one, the intersection will be done between the specified
// rectangle and the previous scissor rectangle transformed in the current
// transform space. The resulting shape is always rectangle.
void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h);
// Reset and disables scissoring.
void nvgResetScissor(NVGcontext* ctx);
//
// Paths
//
// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths.
// Then you define one or more paths and sub-paths which describe the shape. The are functions
// to draw common shapes like rectangles and circles, and lower level step-by-step functions,
// which allow to define a path curve by curve.
//
// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
// winding and holes should have counter clockwise order. To specify winding of a path you can
// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW.
//
// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it
// with current stroke style by calling nvgStroke().
//
// The curve segments and sub-paths are transformed by the current transform.
// Clears the current path and sub-paths.
void nvgBeginPath(NVGcontext* ctx);
// Starts new sub-path with specified point as first point.
void nvgMoveTo(NVGcontext* ctx, float x, float y);
// Adds line segment from the last point in the path to the specified point.
void nvgLineTo(NVGcontext* ctx, float x, float y);
// Adds cubic bezier segment from last point in the path via two control points to the specified point.
void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y);
// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y);
// Adds an arc segment at the corner defined by the last path point, and two specified points.
void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius);
// Closes current sub-path with a line segment.
void nvgClosePath(NVGcontext* ctx);
// Sets the current sub-path winding, see NVGwinding and NVGsolidity.
void nvgPathWinding(NVGcontext* ctx, int dir);
// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r,
// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW).
// Angles are specified in radians.
void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir);
// Creates new rectangle shaped sub-path.
void nvgRect(NVGcontext* ctx, float x, float y, float w, float h);
// Creates new rounded rectangle shaped sub-path.
void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r);
// Creates new rounded rectangle shaped sub-path with varying radii for each corner.
void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft);
// Creates new ellipse shaped sub-path.
void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry);
// Creates new circle shaped sub-path.
void nvgCircle(NVGcontext* ctx, float cx, float cy, float r);
// Fills the current path with current fill style.
void nvgFill(NVGcontext* ctx);
// Fills the current path with current stroke style.
void nvgStroke(NVGcontext* ctx);
//
// Text
//
// NanoVG allows you to load .ttf files and use the font to render text.
//
// The appearance of the text can be defined by setting the current text style
// and by specifying the fill color. Common text and font settings such as
// font size, letter spacing and text align are supported. Font blur allows you
// to create simple text effects such as drop shadows.
//
// At render time the font face can be set based on the font handles or name.
//
// Font measure functions return values in local space, the calculations are
// carried in the same resolution as the final rendering. This is done because
// the text glyph positions are snapped to the nearest pixels sharp rendering.
//
// The local space means that values are not rotated or scale as per the current
// transformation. For example if you set font size to 12, which would mean that
// line height is 16, then regardless of the current scaling and rotation, the
// returned line height is always 16. Some measures may vary because of the scaling
// since aforementioned pixel snapping.
//
// While this may sound a little odd, the setup allows you to always render the
// same way regardless of scaling. I.e. following works regardless of scaling:
//
// const char* txt = "Text me up.";
// nvgTextBounds(vg, x,y, txt, NULL, bounds);
// nvgBeginPath(vg);
// nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
// nvgFill(vg);
//
// Note: currently only solid color fill is supported for text.
// Creates font by loading it from the disk from specified file name.
// Returns handle to the font.
int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename);
// Creates font by loading it from the specified memory chunk.
// Returns handle to the font.
int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData);
// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found.
int nvgFindFont(NVGcontext* ctx, const char* name);
// Adds a fallback font by handle.
int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont);
// Adds a fallback font by name.
int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont);
// Sets the font size of current text style.
void nvgFontSize(NVGcontext* ctx, float size);
// Sets the blur of current text style.
void nvgFontBlur(NVGcontext* ctx, float blur);
// Sets the letter spacing of current text style.
void nvgTextLetterSpacing(NVGcontext* ctx, float spacing);
// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
void nvgTextLineHeight(NVGcontext* ctx, float lineHeight);
// Sets the text align of current text style, see NVGalign for options.
void nvgTextAlign(NVGcontext* ctx, int align);
// Sets the font face based on specified id of current text style.
void nvgFontFaceId(NVGcontext* ctx, int font);
// Sets the font face based on specified name of current text style.
void nvgFontFace(NVGcontext* ctx, const char* font);
// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.
float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end);
// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.
// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end);
// Measures the specified text string. Parameter bounds should be a pointer to float[4],
// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
// Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
// Measured values are returned in local coordinate space.
float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds);
// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],
// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
// Measured values are returned in local coordinate space.
void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds);
// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used.
// Measured values are returned in local coordinate space.
int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions);
// Returns the vertical metrics based on the current text style.
// Measured values are returned in local coordinate space.
void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh);
// Breaks the specified text into lines. If end is specified only the sub-string will be used.
// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows);
//
// Internal Render API
//
enum NVGtexture {
NVG_TEXTURE_ALPHA = 0x01,
NVG_TEXTURE_RGBA = 0x02,
};
struct NVGscissor {
float xform[6];
float extent[2];
};
typedef struct NVGscissor NVGscissor;
struct NVGvertex {
float x,y,u,v;
};
typedef struct NVGvertex NVGvertex;
struct NVGpath {
int first;
int count;
unsigned char closed;
int nbevel;
NVGvertex* fill;
int nfill;
NVGvertex* stroke;
int nstroke;
int winding;
int convex;
};
typedef struct NVGpath NVGpath;
struct NVGparams {
void* userPtr;
int edgeAntiAlias;
int (*renderCreate)(void* uptr);
int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);
int (*renderDeleteTexture)(void* uptr, int image);
int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data);
int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h);
void (*renderViewport)(void* uptr, float width, float height, float devicePixelRatio);
void (*renderCancel)(void* uptr);
void (*renderFlush)(void* uptr);
void (*renderFill)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths);
void (*renderStroke)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths);
void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, const NVGvertex* verts, int nverts);
void (*renderDelete)(void* uptr);
};
typedef struct NVGparams NVGparams;
// Constructor and destructor, called by the render back-end.
NVGcontext* nvgCreateInternal(NVGparams* params);
void nvgDeleteInternal(NVGcontext* ctx);
NVGparams* nvgInternalParams(NVGcontext* ctx);
// Debug function to dump cached path data.
void nvgDebugDumpPathCache(NVGcontext* ctx);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; }
#ifdef __cplusplus
}
#endif
#endif // NANOVG_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,154 @@
//
// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#ifndef NANOVG_GL_UTILS_H
#define NANOVG_GL_UTILS_H
struct NVGLUframebuffer {
NVGcontext* ctx;
GLuint fbo;
GLuint rbo;
GLuint texture;
int image;
};
typedef struct NVGLUframebuffer NVGLUframebuffer;
// Helper function to create GL frame buffer to render to.
void nvgluBindFramebuffer(NVGLUframebuffer* fb);
NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags);
void nvgluDeleteFramebuffer(NVGLUframebuffer* fb);
#endif // NANOVG_GL_UTILS_H
#ifdef NANOVG_GL_IMPLEMENTATION
#if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3)
// FBO is core in OpenGL 3>.
# define NANOVG_FBO_VALID 1
#elif defined(NANOVG_GL2)
// On OS X including glext defines FBO on GL2 too.
# ifdef __APPLE__
# include <OpenGL/glext.h>
# define NANOVG_FBO_VALID 1
# endif
#endif
static GLint defaultFBO = -1;
NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags)
{
#ifdef NANOVG_FBO_VALID
GLint defaultFBO;
GLint defaultRBO;
NVGLUframebuffer* fb = NULL;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO);
fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer));
if (fb == NULL) goto error;
memset(fb, 0, sizeof(NVGLUframebuffer));
fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL);
#if defined NANOVG_GL2
fb->texture = nvglImageHandleGL2(ctx, fb->image);
#elif defined NANOVG_GL3
fb->texture = nvglImageHandleGL3(ctx, fb->image);
#elif defined NANOVG_GLES2
fb->texture = nvglImageHandleGLES2(ctx, fb->image);
#elif defined NANOVG_GLES3
fb->texture = nvglImageHandleGLES3(ctx, fb->image);
#endif
fb->ctx = ctx;
// frame buffer object
glGenFramebuffers(1, &fb->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo);
// render buffer object
glGenRenderbuffers(1, &fb->rbo);
glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h);
// combine all
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
#ifdef GL_DEPTH24_STENCIL8
// If GL_STENCIL_INDEX8 is not supported, try GL_DEPTH24_STENCIL8 as a fallback.
// Some graphics cards require a depth buffer along with a stencil.
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
#endif // GL_DEPTH24_STENCIL8
goto error;
}
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO);
return fb;
error:
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO);
nvgluDeleteFramebuffer(fb);
return NULL;
#else
NVG_NOTUSED(ctx);
NVG_NOTUSED(w);
NVG_NOTUSED(h);
NVG_NOTUSED(imageFlags);
return NULL;
#endif
}
void nvgluBindFramebuffer(NVGLUframebuffer* fb)
{
#ifdef NANOVG_FBO_VALID
if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO);
#else
NVG_NOTUSED(fb);
#endif
}
void nvgluDeleteFramebuffer(NVGLUframebuffer* fb)
{
#ifdef NANOVG_FBO_VALID
if (fb == NULL) return;
if (fb->fbo != 0)
glDeleteFramebuffers(1, &fb->fbo);
if (fb->rbo != 0)
glDeleteRenderbuffers(1, &fb->rbo);
if (fb->image >= 0)
nvgDeleteImage(fb->ctx, fb->image);
fb->ctx = NULL;
fb->fbo = 0;
fb->rbo = 0;
fb->texture = 0;
fb->image = -1;
free(fb);
#else
NVG_NOTUSED(fb);
#endif
}
#endif // NANOVG_GL_IMPLEMENTATION

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <nanovg.h>
#include <borealis/style.hpp>
#include <borealis/theme.hpp>
namespace brls
{
class FontStash
{
public:
int regular = 0;
int korean = 0;
int material = 0;
int sharedSymbols = 0;
};
class FrameContext
{
public:
NVGcontext* vg = nullptr;
float pixelRatio = 0.0;
FontStash* fontStash = nullptr;
ThemeValues* theme = nullptr;
};
} // namespace brls

View File

@@ -0,0 +1,42 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
namespace brls
{
// A simple header with text, a rectangle on the left
// and a separator
class Header : public View
{
private:
std::string label;
std::string sublabel;
bool separator;
public:
Header(std::string label, bool separator = true, std::string sublabel = "");
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
};
} // namespace brls

View File

@@ -0,0 +1,63 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2020 WerWolv
Copyright (C) 2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/box_layout.hpp>
#include <borealis/label.hpp>
#include <borealis/view.hpp>
namespace brls
{
// Displays button hints for the currently focused view
// Depending on the view's available actions
// there can only be one Hint visible at any time
class Hint : public BoxLayout
{
private:
bool animate;
GenericEvent::Subscription globalFocusEventSubscriptor;
VoidEvent::Subscription globalHintsUpdateEventSubscriptor;
static inline std::vector<Hint*> globalHintStack;
static void pushHint(Hint* hint);
static void popHint(Hint* hint);
static void animateHints();
static std::string getKeyIcon(Key key);
void rebuildHints();
public:
Hint(bool animate = true);
~Hint();
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
void setAnimate(bool animate)
{
this->animate = animate;
}
};
} // namespace brls

View File

@@ -0,0 +1,78 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/frame_context.hpp>
#include <borealis/view.hpp>
namespace brls
{
enum class ImageScaleType
{
NO_RESIZE = 0, // Nothing is resized
FIT, // The image is shrinked to fit the view boundaries
CROP, // The image is not resized but is cropped if bigger than the view
SCALE, // The image is stretched to match the view boundaries
VIEW_RESIZE // The view is resized to match the image
};
// An image
class Image : public View
{
public:
Image(std::string imagePath);
Image(unsigned char* buffer, size_t bufferSize);
~Image();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void setImage(unsigned char* buffer, size_t bufferSize);
void setImage(std::string imagePath);
void setScaleType(ImageScaleType imageScaleType);
void setOpacity(float opacity);
void setCornerRadius(float radius)
{
this->cornerRadius = radius;
}
private:
std::string imagePath;
unsigned char* imageBuffer = nullptr;
size_t imageBufferSize = 0;
int texture = -1;
NVGpaint imgPaint;
ImageScaleType imageScaleType = ImageScaleType::FIT;
float cornerRadius = 0;
int imageX = 0, imageY = 0;
int imageWidth = 0, imageHeight = 0;
int origViewWidth = 0, origViewHeight = 0;
void reloadTexture();
};
} // namespace brls

View File

@@ -0,0 +1,112 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
namespace brls
{
enum class LabelStyle
{
REGULAR = 0,
MEDIUM,
SMALL,
DESCRIPTION,
CRASH,
BUTTON_PLAIN,
BUTTON_PLAIN_DISABLED,
BUTTON_BORDERLESS,
LIST_ITEM,
NOTIFICATION,
DIALOG,
BUTTON_DIALOG,
HINT
};
// A Label, multiline or with a ticker
class Label : public View
{
private:
std::string text;
bool multiline;
unsigned fontSize;
float lineHeight;
LabelStyle labelStyle;
NVGalign horizontalAlign = NVG_ALIGN_LEFT;
NVGalign verticalAlign = NVG_ALIGN_MIDDLE;
NVGcolor customColor;
bool useCustomColor = false;
int customFont;
bool useCustomFont = false;
public:
Label(LabelStyle labelStyle, std::string text, bool multiline = false);
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void setVerticalAlign(NVGalign align);
void setHorizontalAlign(NVGalign align);
void setText(std::string text);
void setStyle(LabelStyle style);
void setFontSize(unsigned size);
/**
* Sets the label color
*/
void setColor(NVGcolor color);
/**
* Unsets the label color - it
* will now use the default one
* for the label style
*/
void unsetColor();
/**
* Returns the effective label color
* = custom or the style default
*/
NVGcolor getColor(ThemeValues* theme);
/**
* Sets the font id
*/
void setFont(int fontId);
/**
* Unsets the font id - it
* will now use the regular one
*/
void unsetFont();
/**
* Returns the font used
* = custom or the regular font
*/
int getFont(FontStash* stash);
};
} // namespace brls

View File

@@ -0,0 +1,51 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 WerWolv
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
#include <vector>
namespace brls
{
// A view containing multiple children views with the ability to freely switch between these layers
class LayerView : public View
{
public:
LayerView();
~LayerView();
void addLayer(View* view);
void changeLayer(int index, bool focus = false);
int getLayerIndex();
View* getDefaultFocus() override;
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
private:
std::vector<View*> layers;
int selectedIndex = 0;
};
}

View File

@@ -0,0 +1,218 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/box_layout.hpp>
#include <borealis/image.hpp>
#include <borealis/label.hpp>
#include <borealis/rectangle.hpp>
#include <borealis/scroll_view.hpp>
#include <string>
namespace brls
{
// A list item
// TODO: Use a Label with integrated ticker
class ListItem : public View
{
private:
std::string label;
std::string subLabel;
std::string value;
bool valueFaint;
std::string oldValue;
bool oldValueFaint;
float valueAnimation = 0.0f;
bool checked = false; // check mark on the right
unsigned textSize;
bool drawTopSeparator = true;
Label* descriptionView = nullptr;
Image* thumbnailView = nullptr;
bool reduceDescriptionSpacing = false;
GenericEvent clickEvent;
bool indented = false;
void resetValueAnimation();
public:
ListItem(std::string label, std::string description = "", std::string subLabel = "");
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void getHighlightInsets(unsigned* top, unsigned* right, unsigned* bottom, unsigned* left) override;
virtual bool onClick();
View* getDefaultFocus() override;
void setThumbnail(Image* image);
void setThumbnail(std::string imagePath);
void setThumbnail(unsigned char* buffer, size_t bufferSize);
bool hasDescription();
void setDrawTopSeparator(bool draw);
bool getReduceDescriptionSpacing();
void setReduceDescriptionSpacing(bool value);
void setIndented(bool indented);
void setTextSize(unsigned textSize);
void setChecked(bool checked);
std::string getLabel();
/**
* Sets the value of this list item
* (the text on the right)
* Set faint to true to have the new value
* use a darker color (typically "OFF" labels)
*/
void setValue(std::string value, bool faint = false, bool animate = true);
std::string getValue();
GenericEvent* getClickEvent();
~ListItem();
};
// Some spacing (to make groups of ListItems)
class ListItemGroupSpacing : public Rectangle
{
public:
ListItemGroupSpacing(bool separator = false);
};
// A list item with mutliple choices for its value
// (will open a Dropdown)
// Fired when the user has selected a value
//
// Parameter is either the selected value index
// or -1 if the user cancelled
typedef Event<int> ValueSelectedEvent;
class SelectListItem : public ListItem
{
public:
SelectListItem(std::string label, std::vector<std::string> values, unsigned selectedValue = 0);
void setSelectedValue(unsigned value);
ValueSelectedEvent* getValueSelectedEvent();
private:
std::vector<std::string> values;
unsigned selectedValue;
ValueSelectedEvent valueEvent;
};
// A list item with a ON/OFF value
// that can be toggled
// Use the click event to detect when the value
// changes
class ToggleListItem : public ListItem
{
private:
bool toggleState;
std::string onValue, offValue;
void updateValue();
public:
ToggleListItem(std::string label, bool initialValue, std::string description = "", std::string onValue = "On", std::string offValue = "Off");
virtual bool onClick() override;
bool getToggleState();
};
// A list item which spawns the swkbd
// to input its value (string)
class InputListItem : public ListItem
{
protected:
std::string helpText;
int maxInputLength;
public:
InputListItem(std::string label, std::string initialValue, std::string helpText, std::string description = "", int maxInputLength = 32);
virtual bool onClick() override;
};
// A list item which spawns the swkbd
// to input its value (integer)
class IntegerInputListItem : public InputListItem
{
public:
IntegerInputListItem(std::string label, int initialValue, std::string helpText, std::string description = "", int maxInputLength = 32);
virtual bool onClick() override;
};
class List; // forward declaration for ListContentView::list
// The content view of lists (used internally)
class ListContentView : public BoxLayout
{
public:
ListContentView(List* list, size_t defaultFocus = 0);
protected:
void customSpacing(View* current, View* next, int* spacing) override;
private:
List* list;
};
// A vertical list of various widgets, with proper margins and spacing
// and a scroll bar
// In practice it's a ScrollView which content view is
// a ListContentView (BoxLayout)
class List : public ScrollView
{
private:
ListContentView* layout;
public:
List(size_t defaultFocus = 0);
~List();
// Wrapped BoxLayout methods
void addView(View* view, bool fill = false);
void clear(bool free = true);
void setMargins(unsigned top, unsigned right, unsigned bottom, unsigned left);
void setMarginBottom(unsigned bottom);
void setSpacing(unsigned spacing);
unsigned getSpacing();
virtual void customSpacing(View* current, View* next, int* spacing);
};
} // namespace brls

View File

@@ -0,0 +1,49 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdarg.h>
#include <string>
namespace brls
{
enum class LogLevel
{
ERROR = 0,
INFO,
DEBUG
};
class Logger
{
public:
static void setLogLevel(LogLevel logLevel);
static void error(const char* format, ...);
static void info(const char* format, ...);
static void debug(const char* format, ...);
protected:
static void log(LogLevel logLevel, const char* prefix, const char* color, const char* format, va_list ap);
};
} // namespace brls

View File

@@ -0,0 +1,40 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
namespace brls
{
// A Material icon (from the Material font)
class MaterialIcon : public View
{
private:
std::string icon;
unsigned middleX, middleY;
public:
MaterialIcon(std::string icon);
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
};
} // namespace brls

View File

@@ -0,0 +1,64 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/animations.hpp>
#include <borealis/label.hpp>
#include <borealis/view.hpp>
#define BRLS_NOTIFICATIONS_MAX 8
// TODO: check in HOS that the animation duration + notification timeout are correct
namespace brls
{
class Notification : public View
{
public:
Notification(std::string text);
~Notification();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
menu_timer_t timeoutTimer;
private:
Label* label;
};
class NotificationManager : public View
{
private:
Notification* notifications[BRLS_NOTIFICATIONS_MAX];
void layoutNotification(size_t i);
public:
NotificationManager();
~NotificationManager();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void notify(std::string text);
};
}; // namespace brls

View File

@@ -0,0 +1,63 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/applet_frame.hpp>
#include <borealis/list.hpp>
#include <borealis/view.hpp>
#include <string>
namespace brls
{
class PopupFrame : public View
{
private:
PopupFrame(std::string title, unsigned char* imageBuffer, size_t imageBufferSize, AppletFrame* contentView, std::string subTitleLeft = "", std::string subTitleRight = "");
PopupFrame(std::string title, std::string imagePath, AppletFrame* contentView, std::string subTitleLeft = "", std::string subTitleRight = "");
PopupFrame(std::string title, AppletFrame* contentView, std::string subTitleLeft = "", std::string subTitleRight = "");
AppletFrame* contentView = nullptr;
protected:
unsigned getShowAnimationDuration(ViewAnimation animation) override;
public:
~PopupFrame();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
View* getDefaultFocus() override;
virtual bool onCancel();
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
static void open(std::string title, unsigned char* imageBuffer, size_t imageBufferSize, AppletFrame* contentView, std::string subTitleLeft = "", std::string subTitleRight = "");
static void open(std::string title, std::string imagePath, AppletFrame* contentView, std::string subTitleLeft = "", std::string subTitleRight = "");
static void open(std::string title, AppletFrame* contentView, std::string subTitleLeft = "", std::string subTitleRight = "");
bool isTranslucent() override
{
return true;
}
};
} // namespace brls

View File

@@ -0,0 +1,58 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 Billy Laws
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/label.hpp>
#include <borealis/progress_spinner.hpp>
#include <borealis/view.hpp>
namespace brls
{
// TODO: Add the "ProgressDisplayFlags_" prefix to the members
enum ProgressDisplayFlags
{
SPINNER = 1u << 0,
PERCENTAGE = 1u << 1
};
inline constexpr ProgressDisplayFlags DEFAULT_PROGRESS_DISPLAY_FLAGS = (ProgressDisplayFlags)(ProgressDisplayFlags::SPINNER | ProgressDisplayFlags::PERCENTAGE);
// A progress bar with an optional spinner and percentage text.
class ProgressDisplay : public View
{
public:
ProgressDisplay(ProgressDisplayFlags progressFlags = DEFAULT_PROGRESS_DISPLAY_FLAGS);
~ProgressDisplay();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
void setProgress(int current, int max);
private:
float progressPercentage = 0.0f;
Label* label;
ProgressSpinner* spinner;
};
} // namespace brls

View File

@@ -0,0 +1,44 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 Billy Laws
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
namespace brls
{
// A progress spinner
class ProgressSpinner : public View
{
public:
ProgressSpinner();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
private:
float animationValue = 0.0f;
void restartAnimation();
};
} // namespace brls

View File

@@ -0,0 +1,47 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/frame_context.hpp>
#include <borealis/view.hpp>
namespace brls
{
// A solid color rectangle
class Rectangle : public View
{
protected:
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
public:
Rectangle(NVGcolor color);
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void setColor(NVGcolor color);
~Rectangle() {}
private:
NVGcolor color = nvgRGB(0, 0, 255);
};
} // namespace brls

View File

@@ -0,0 +1,87 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <features/features_cpu.h>
namespace brls
{
// A task that is repeated at a given interval
// by the UI thread
class RepeatingTask
{
private:
retro_time_t interval;
retro_time_t lastRun = 0;
bool running = false;
bool stopRequested = false;
public:
RepeatingTask(retro_time_t interval);
virtual ~RepeatingTask();
/**
* Actual code to run by the task
* Must call RepeatingTask::run() !
*/
virtual void run(retro_time_t currentTime);
/**
* Fired when the task starts
*/
virtual void onStart() {};
/**
* Fired when the task stops
*/
virtual void onStop() {};
/**
* Starts the task
*/
void start();
/**
* Fires the task immediately and delays the
* next run
*/
void fireNow();
/**
* Pauses the task without deleting it
*/
void pause();
/**
* Stops and deletes the task
*/
void stop();
retro_time_t getInterval();
retro_time_t getLastRun();
bool isRunning();
bool isStopRequested();
};
} // namespace brls

View File

@@ -0,0 +1,65 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/view.hpp>
namespace brls
{
// TODO: horizontal scrolling, either in ScrollView or in a separate class (like Android has)
// A view that automatically scrolls vertically
// when one of its children gains focus
class ScrollView : public View
{
private:
View* contentView = nullptr;
bool ready = false; // has layout been called at least once?
unsigned middleY = 0; // y + height/2
unsigned bottomY = 0; // y + height
float scrollY = 0.0f; // from 0.0f to 1.0f, in % of content view height
bool updateScrollingOnNextLayout = false;
bool updateScrollingOnNextFrame = false;
unsigned getYCenter(View* view);
void prebakeScrolling();
bool updateScrolling(bool animated);
void startScrolling(bool animated, float newScroll);
void scrollAnimationTick();
public:
~ScrollView();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
View* getDefaultFocus() override;
void onChildFocusGained(View* child) override;
void onWindowSizeChanged() override;
void setContentView(View* view);
View* getContentView();
};
} // namespace brls

View File

@@ -0,0 +1,94 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/box_layout.hpp>
#include <string>
#include <vector>
namespace brls
{
// A sidebar with multiple tabs
class SidebarSeparator : public View
{
public:
SidebarSeparator();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
};
class Sidebar;
// TODO: Use a Label view with integrated ticker for label and sublabel
// TODO: Have the label always tick when active
class SidebarItem : public View
{
private:
std::string label;
bool active = false;
Sidebar* sidebar = nullptr;
View* associatedView = nullptr;
public:
SidebarItem(std::string label, Sidebar* sidebar);
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
View* getDefaultFocus() override
{
return this;
}
virtual bool onClick();
void setActive(bool active);
bool isActive();
void onFocusGained() override;
void setAssociatedView(View* view);
View* getAssociatedView();
~SidebarItem();
};
// TODO: Add a style with icons, make it collapsible?
class Sidebar : public BoxLayout
{
private:
SidebarItem* currentActive = nullptr;
public:
Sidebar();
SidebarItem* addItem(std::string label, View* view);
void addSeparator();
void setActive(SidebarItem* item);
View* getDefaultFocus() override;
void onChildFocusGained(View* child) override;
size_t lastFocus = 0;
};
} // namespace brls

View File

@@ -0,0 +1,54 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 Billy Laws
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/applet_frame.hpp>
#include <borealis/sidebar.hpp>
#include <string>
#include <vector>
namespace brls
{
// An applet frame for implementing a stage based app with a progress display in top right
class StagedAppletFrame : public AppletFrame
{
public:
StagedAppletFrame();
~StagedAppletFrame();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void addStage(View* view);
void nextStage();
void previousStage();
unsigned getCurrentStage();
unsigned getStagesCount();
unsigned isLastStage();
private:
size_t currentStage = 0;
std::vector<View*> stageViews;
void enterStage(int index, bool requestFocus);
};
} // namespace brls

View File

@@ -0,0 +1,323 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
namespace brls
{
class Style
{
public:
// AppletFrame
struct
{
unsigned headerHeightRegular;
unsigned headerHeightPopup; // PopupFrame
unsigned footerHeight;
unsigned imageLeftPadding;
unsigned imageTopPadding;
unsigned imageSize;
unsigned separatorSpacing;
unsigned titleSize;
unsigned titleStart;
unsigned titleOffset;
unsigned footerTextSize;
unsigned footerTextSpacing;
unsigned slideAnimation;
} AppletFrame;
// Highlight
struct
{
unsigned strokeWidth;
float cornerRadius;
unsigned shadowWidth;
unsigned shadowOffset;
unsigned shadowFeather;
unsigned shadowOpacity;
unsigned animationDuration;
} Highlight;
// Background
struct
{
unsigned sidebarBorderHeight;
} Background;
// Sidebar
struct
{
unsigned width;
unsigned spacing;
unsigned marginLeft;
unsigned marginRight;
unsigned marginTop;
unsigned marginBottom;
struct
{
unsigned height;
unsigned textSize;
unsigned padding;
unsigned textOffsetX;
unsigned activeMarkerWidth;
unsigned highlight;
} Item;
struct
{
unsigned height;
} Separator;
} Sidebar;
// List
struct
{
unsigned marginLeftRight;
unsigned marginTopBottom;
unsigned spacing;
// Item
struct
{
unsigned height;
unsigned heightWithSubLabel;
unsigned valueSize;
unsigned padding;
unsigned thumbnailPadding;
unsigned descriptionIndent;
unsigned descriptionSpacing;
unsigned indent;
unsigned selectRadius;
} Item;
} List;
// Label
struct
{
unsigned regularFontSize;
unsigned mediumFontSize;
unsigned smallFontSize;
unsigned descriptionFontSize;
unsigned crashFontSize;
unsigned buttonFontSize;
unsigned listItemFontSize;
unsigned notificationFontSize;
unsigned dialogFontSize;
unsigned hintFontSize;
float lineHeight;
float notificationLineHeight;
} Label;
// CrashFrame
struct
{
float labelWidth; // proportional to frame width, from 0 to 1
unsigned boxStrokeWidth;
unsigned boxSize;
unsigned boxSpacing;
unsigned buttonWidth;
unsigned buttonHeight;
unsigned buttonSpacing;
} CrashFrame;
// Button
struct
{
float cornerRadius;
unsigned highlightInset;
float shadowWidth;
float shadowFeather;
float shadowOpacity;
float shadowOffset;
} Button;
// TableRow
struct
{
unsigned headerHeight;
unsigned headerTextSize;
unsigned bodyHeight;
unsigned bodyIndent;
unsigned bodyTextSize;
unsigned padding;
} TableRow;
// Dropdown
struct
{
unsigned listWidth;
unsigned listPadding;
unsigned listItemHeight;
unsigned listItemTextSize;
unsigned headerHeight;
unsigned headerFontSize;
unsigned headerPadding;
} Dropdown;
struct
{
unsigned edgePadding;
unsigned separatorSpacing;
unsigned footerHeight;
unsigned imageLeftPadding;
unsigned imageTopPadding;
unsigned imageSize;
unsigned contentWidth;
unsigned contentHeight;
unsigned headerTextLeftPadding;
unsigned headerTextTopPadding;
unsigned subTitleLeftPadding;
unsigned subTitleTopPadding;
unsigned subTitleSpacing;
unsigned subTitleSeparatorLeftPadding;
unsigned subTitleSeparatorTopPadding;
unsigned subTitleSeparatorHeight;
unsigned headerFontSize;
unsigned subTitleFontSize;
} PopupFrame;
// StagedAppletFrame
struct
{
unsigned progressIndicatorSpacing;
unsigned progressIndicatorRadiusUnselected;
unsigned progressIndicatorRadiusSelected;
unsigned progressIndicatorBorderWidth;
} StagedAppletFrame;
// ProgressSpinner
struct
{
float centerGapMultiplier;
float barWidthMultiplier;
unsigned animationDuration;
} ProgressSpinner;
// ProgressDisplay
struct
{
unsigned percentageLabelWidth;
} ProgressDisplay;
// Header
struct
{
unsigned height;
unsigned padding;
unsigned rectangleWidth;
unsigned fontSize;
} Header;
// FramerateCounter
struct
{
unsigned width;
unsigned height;
} FramerateCounter;
// ThumbnailSidebar
struct
{
unsigned marginLeftRight;
unsigned marginTopBottom;
unsigned buttonHeight;
unsigned buttonMargin;
} ThumbnailSidebar;
// AnimationDuration
struct
{
unsigned show;
unsigned showSlide;
unsigned highlight;
unsigned shake;
unsigned collapse;
unsigned progress;
unsigned notificationTimeout;
} AnimationDuration;
// Notification
struct
{
unsigned width;
unsigned padding;
unsigned slideAnimation;
} Notification;
// Dialog
struct
{
unsigned width;
unsigned height;
unsigned paddingTopBottom;
unsigned paddingLeftRight;
float cornerRadius;
unsigned buttonHeight;
unsigned buttonSeparatorHeight;
float shadowWidth;
float shadowFeather;
float shadowOpacity;
float shadowOffset;
} Dialog;
// As close to HOS as possible
static Style horizon();
// TODO: Make a condensed style
};
} // namespace brls

View File

@@ -0,0 +1,35 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <string>
namespace brls
{
class Swkbd
{
public:
static bool openForText(std::function<void(std::string)> f, std::string headerText = "", std::string subText = "", int maxStringLength = 32, std::string initialText = "");
static bool openForNumber(std::function<void(int)> f, std::string headerText = "", std::string subText = "", int maxStringLength = 32, std::string initialText = "", std::string leftButton = "", std::string rightButton = "");
};
} // namespace brls

View File

@@ -0,0 +1,59 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/applet_frame.hpp>
#include <borealis/sidebar.hpp>
#include <string>
#include <vector>
namespace brls
{
// An applet frame containing a sidebar on the left with multiple tabs
class TabFrame : public AppletFrame
{
public:
TabFrame();
/**
* Adds a tab with given label and view
* All tabs and separators must be added
* before the TabFrame is itself added to
* the view hierarchy
*/
void addTab(std::string label, View* view);
void addSeparator();
View* getDefaultFocus() override;
virtual bool onCancel() override;
~TabFrame();
private:
Sidebar* sidebar;
BoxLayout* layout;
View* rightPane = nullptr;
void switchToView(View* view);
};
} // namespace brls

View File

@@ -0,0 +1,73 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/view.hpp>
#include <string>
#include <vector>
namespace brls
{
// The type of a Table Row
enum class TableRowType
{
HEADER = 0,
BODY
};
// A Table row
class TableRow
{
private:
TableRowType type;
std::string label;
std::string value;
public:
TableRow(TableRowType type, std::string label, std::string value = "");
std::string* getLabel();
std::string* getValue();
TableRowType getType();
void setValue(std::string value);
};
// A simple, static two-columns table, as seen in
// the Settings app (Internet connection details)
// All rows must be added before adding the view
// to a layout (it's static)
class Table : public View
{
private:
std::vector<TableRow*> rows;
public:
~Table();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
TableRow* addRow(TableRowType type, std::string label, std::string value = "");
};
} // namespace brls

View File

@@ -0,0 +1,43 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/repeating_task.hpp>
#include <vector>
namespace brls
{
class TaskManager
{
private:
std::vector<RepeatingTask*> repeatingTasks;
void stopRepeatingTask(RepeatingTask* task);
public:
void frame();
void registerRepeatingTask(RepeatingTask* task);
~TaskManager();
};
} // namespace brls

View File

@@ -0,0 +1,96 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <nanovg.h>
namespace brls
{
// Theme variants
// (used in Application)
//
// Not an enum class because it's used
// as an array index in Theme
enum ThemeVariant
{
ThemeVariant_LIGHT = 0,
ThemeVariant_DARK,
ThemeVariant_NUMBER_OF_VARIANTS
};
typedef struct
{
float backgroundColor[3]; // gl color
NVGcolor backgroundColorRGB;
NVGcolor textColor;
NVGcolor descriptionColor;
NVGcolor notificationTextColor;
NVGcolor backdropColor;
NVGcolor separatorColor;
NVGcolor sidebarColor;
NVGcolor activeTabColor;
NVGcolor sidebarSeparatorColor;
NVGcolor highlightBackgroundColor;
NVGcolor highlightColor1;
NVGcolor highlightColor2;
NVGcolor listItemSeparatorColor;
NVGcolor listItemValueColor;
NVGcolor listItemFaintValueColor;
NVGcolor tableEvenBackgroundColor;
NVGcolor tableBodyTextColor;
NVGcolor dropdownBackgroundColor;
NVGcolor nextStageBulletColor;
NVGcolor spinnerBarColor;
NVGcolor headerRectangleColor;
NVGcolor buttonPlainEnabledBackgroundColor;
NVGcolor buttonPlainDisabledBackgroundColor;
NVGcolor buttonPlainEnabledTextColor;
NVGcolor buttonPlainDisabledTextColor;
NVGcolor dialogColor;
NVGcolor dialogBackdrop;
NVGcolor dialogButtonColor;
NVGcolor dialogButtonSeparatorColor;
} ThemeValues;
// A theme contains colors for all variants
class Theme
{
public:
ThemeValues colors[ThemeVariant_NUMBER_OF_VARIANTS];
// As close to HOS as possible
static Theme horizon();
};
} // namespace brls

View File

@@ -0,0 +1,78 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <borealis/applet_frame.hpp>
#include <borealis/box_layout.hpp>
#include <borealis/button.hpp>
#include <borealis/image.hpp>
namespace brls
{
// The sidebar used in ThumbnailFrame
class ThumbnailSidebar : public View
{
private:
Image* image = nullptr;
Button* button = nullptr;
Label* title = nullptr;
Label* subTitle = nullptr;
public:
ThumbnailSidebar();
~ThumbnailSidebar();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) override;
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
View* getDefaultFocus() override;
void setThumbnail(std::string imagePath);
void setThumbnail(unsigned char* buffer, size_t bufferSize);
void setTitle(std::string title);
void setSubtitle(std::string subTitle);
Button* getButton();
};
// An applet frame with a sidebar on the right, containing a thumbnail
// and a button (similar to the Wi-Fi settings in HOS)
class ThumbnailFrame : public AppletFrame
{
private:
ThumbnailSidebar* sidebar = nullptr;
BoxLayout* boxLayout = nullptr;
View* thumbnailContentView = nullptr;
public:
ThumbnailFrame();
~ThumbnailFrame();
void setContentView(View* view) override;
protected:
void layout(NVGcontext* vg, Style* style, FontStash* stash) override;
ThumbnailSidebar* getSidebar();
};
} // namespace brls

View File

@@ -0,0 +1,406 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <features/features_cpu.h>
#include <stdio.h>
#include <borealis/actions.hpp>
#include <borealis/event.hpp>
#include <borealis/frame_context.hpp>
#include <functional>
#include <map>
#include <string>
#include <vector>
namespace brls
{
// The animation to play when
// pushing or popping a view
// (implemented in Application)
enum class ViewAnimation
{
FADE, // the old view fades away and the new one fades in
SLIDE_LEFT, // the old view slides out to the left and the new one slides in from the right
SLIDE_RIGHT // inverted SLIDE_LEFT
};
// Focus direction when navigating
enum class FocusDirection
{
UP,
DOWN,
LEFT,
RIGHT
};
// View background
enum class Background
{
NONE,
SIDEBAR,
DEBUG,
BACKDROP
};
extern NVGcolor transparent;
class View;
typedef Event<View*> GenericEvent;
typedef Event<> VoidEvent;
// Superclass for all the other views
// Lifecycle of a view is :
// new -> [willAppear -> willDisappear] -> delete
//
// Users have do to the new, the rest of the lifecycle is taken
// care of by the library
//
// willAppear and willDisappear can be called zero or multiple times
// before deletion (in case of a TabLayout for instance)
class View
{
private:
Background background = Background::NONE;
void drawBackground(NVGcontext* vg, FrameContext* ctx, Style* style);
void drawHighlight(NVGcontext* vg, ThemeValues* theme, float alpha, Style* style, bool background);
float highlightAlpha = 0.0f;
bool dirty = true;
bool highlightShaking = false;
retro_time_t highlightShakeStart;
FocusDirection highlightShakeDirection;
float highlightShakeAmplitude;
bool fadeIn = false; // is the fade in animation running?
bool forceTranslucent = false;
ThemeValues* themeOverride = nullptr;
bool hidden = false;
std::vector<Action> actions;
/**
* Parent user data, typically the index of the view
* in the internal layout structure
*/
void* parentUserdata = nullptr;
protected:
int x = 0;
int y = 0;
unsigned width = 0;
unsigned height = 0;
float collapseState = 1.0f;
bool focused = false;
View* parent = nullptr;
GenericEvent focusEvent;
virtual unsigned getShowAnimationDuration(ViewAnimation animation);
virtual void getHighlightInsets(unsigned* top, unsigned* right, unsigned* bottom, unsigned* left)
{
*top = 0;
*right = 0;
*bottom = 0;
*left = 0;
}
virtual void getHighlightMetrics(Style* style, float* cornerRadius)
{
*cornerRadius = style->Highlight.cornerRadius;
}
virtual bool isHighlightBackgroundEnabled()
{
return true;
}
// Helper functions to apply this view's alpha to a color
NVGcolor a(NVGcolor color);
NVGpaint a(NVGpaint paint);
NVGcolor RGB(unsigned r, unsigned g, unsigned b)
{
return this->a(nvgRGB(r, g, b));
}
NVGcolor RGBA(unsigned r, unsigned g, unsigned b, unsigned a)
{
return this->a(nvgRGBA(r, g, b, a));
}
NVGcolor RGBf(float r, float g, float b)
{
return this->a(nvgRGBf(r, g, b));
}
NVGcolor RGBAf(float r, float g, float b, float a)
{
return this->a(nvgRGBAf(r, g, b, a));
}
/**
* Should the hint alpha be animated when
* pushing the view?
*/
virtual bool animateHint()
{
return false;
}
public:
void setBoundaries(int x, int y, unsigned width, unsigned height);
void setBackground(Background background);
void setWidth(unsigned width);
void setHeight(unsigned height);
void shakeHighlight(FocusDirection direction);
int getX();
int getY();
unsigned getWidth();
unsigned getHeight(bool includeCollapse = true);
void setForceTranslucent(bool translucent);
void setParent(View* parent, void* parentUserdata = nullptr);
View* getParent();
bool hasParent();
void* getParentUserData();
void registerAction(std::string hintText, Key key, ActionListener actionListener, bool hidden = false);
void updateActionHint(Key key, std::string hintText);
void setActionAvailable(Key key, bool available);
std::string describe() const { return typeid(*this).name(); }
const std::vector<Action>& getActions()
{
return this->actions;
}
/**
* Called each frame
* Do not override it to draw your view,
* override draw() instead
*/
virtual void frame(FrameContext* ctx);
/**
* Called by frame() to draw
* the view onscreen
*/
virtual void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx) = 0;
/**
* Triggered when the view has been
* resized and needs to layout its
* children
*/
virtual void layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Nothing to do
}
/**
* Called when the view will appear
* on screen, before or after layout()
*
* Can be called if the view has
* already appeared, so be careful
*/
virtual void willAppear(bool resetState = false)
{
// Nothing to do
}
/**
* Called when the view will disappear
* from the screen
*
* Can be called if the view has
* already disappeared, so be careful
*/
virtual void willDisappear(bool resetState = false)
{
// Nothing to do
}
/**
* Called when the show() animation (fade in)
* ends
*/
virtual void onShowAnimationEnd() {};
/**
* Shows the view (fade in animation)
*
* Called once when the view is
* pushed to the view stack
*
* Not recursive
*/
virtual void show(std::function<void(void)> cb, bool animate = true, ViewAnimation animation = ViewAnimation::FADE);
/**
* Hides the view in a collapse animation
*/
void collapse(bool animated = true);
bool isCollapsed();
/**
* Shows the view in a expand animation (opposite
* of collapse)
*/
void expand(bool animated = true);
/**
* Hides the view (fade out animation)
*
* Called if another view is pushed
* on top of this one
*
* Not recursive
*/
virtual void hide(std::function<void(void)> cb, bool animated = true, ViewAnimation animation = ViewAnimation::FADE);
bool isHidden();
/**
* Calls layout() on next frame
* unless immediate is true in which case
* it's called immediately
*/
void invalidate(bool immediate = false);
/**
* Is this view translucent?
*
* If you override it please return
* <value> || View::isTranslucent()
* to keep the fadeIn transition
*/
virtual bool isTranslucent()
{
return fadeIn || forceTranslucent;
}
bool isFocused();
/**
* Returns the default view to focus when focusing this view
* Typically the view itself or one of its children
*
* Returning nullptr means that the view is not focusable
* (and neither are its children)
*
* When pressing a key, the flow is :
* 1. starting from the currently focused view's parent, traverse the tree upwards and
* repeatidly call getNextFocus() on every view until we find a next view to focus or meet the end of the tree
* 2. if a view is found, getNextFocus() will internally call getDefaultFocus() for the selected child
* 3. give focus to the result, if it exists
*/
virtual View* getDefaultFocus()
{
return nullptr;
}
/**
* Returns the next view to focus given the requested direction
* and the currently focused view (as parent user data)
*
* Returning nullptr means that there is no next view to focus
* in that direction - getNextFocus will then be called on our
* parent if any
*/
virtual View* getNextFocus(FocusDirection direction, void* parentUserdata)
{
return nullptr;
}
/**
* Fired when focus is gained
*/
virtual void onFocusGained();
/**
* Fired when focus is lost
*/
virtual void onFocusLost();
/**
* Fired when focus is gained on one of this view's children
*/
virtual void onChildFocusGained(View* child)
{
if (this->hasParent())
this->getParent()->onChildFocusGained(this);
}
/**
* Fired when focus is gained on one of this view's children
*/
virtual void onChildFocusLost(View* child)
{
if (this->hasParent())
this->getParent()->onChildFocusLost(this);
}
/**
* Fired when the window size changes
* Not guaranteed to be called before or after layout()
*/
virtual void onWindowSizeChanged()
{
// Nothing by default
}
GenericEvent* getFocusEvent();
float alpha = 1.0f;
virtual float getAlpha(bool child = false);
/**
* Forces this view and its children to use
* the specified theme variant
*/
void overrideThemeVariant(ThemeValues* newTheme);
virtual ~View();
};
} // namespace brls

View File

@@ -0,0 +1,933 @@
/* RetroArch - A frontend for libretro.
* Borealis, a Nintendo Switch UI Library
* Copyright (C) 2014-2018 - Jean-André Santoni
* Copyright (C) 2011-2018 - Daniel De Matteis
* Copyright (C) 2019 - natinusala
* Copyright (C) 2019 - p-sam
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <compat/strl.h>
#include <encodings/utf.h>
#include <features/features_cpu.h>
#include <math.h>
#include <retro_assert.h>
#include <retro_math.h>
#include <retro_miscellaneous.h>
#include <stdio.h>
#include <string.h>
#include <string/stdstring.h>
#include <borealis/animations.hpp>
#include <vector>
namespace brls
{
struct tween
{
float duration;
float running_since;
float initial_value;
float target_value;
float* subject;
uintptr_t tag;
easing_cb easing;
tween_cb cb;
tween_cb tick;
void* userdata;
bool deleted;
};
struct menu_animation
{
std::vector<tween> list;
std::vector<tween> pending;
bool pending_deletes;
bool in_update;
};
typedef struct menu_animation menu_animation_t;
#define TICKER_SPEED 333
#define TICKER_SLOW_SPEED 1600
static const char ticker_spacer_default[] = " | ";
static menu_animation_t anim;
static retro_time_t cur_time = 0;
static retro_time_t old_time = 0;
static uint64_t ticker_idx = 0; /* updated every TICKER_SPEED ms */
static uint64_t ticker_slow_idx = 0; /* updated every TICKER_SLOW_SPEED ms */
static float delta_time = 0.0f;
static bool animation_is_active = false;
static bool ticker_is_active = false;
static double highlight_gradient_x = 0.0f;
static double highlight_gradient_y = 0.0f;
static double highlight_color = 0.0f;
/* from https://github.com/kikito/tween.lua/blob/master/tween.lua */
static float easing_linear(float t, float b, float c, float d)
{
return c * t / d + b;
}
static float easing_in_out_quad(float t, float b, float c, float d)
{
t = t / d * 2;
if (t < 1)
return c / 2 * pow(t, 2) + b;
return -c / 2 * ((t - 1) * (t - 3) - 1) + b;
}
static float easing_in_quad(float t, float b, float c, float d)
{
return c * pow(t / d, 2) + b;
}
static float easing_out_quad(float t, float b, float c, float d)
{
t = t / d;
return -c * t * (t - 2) + b;
}
static float easing_out_in_quad(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_quad(t * 2, b, c / 2, d);
return easing_in_quad((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_in_cubic(float t, float b, float c, float d)
{
return c * pow(t / d, 3) + b;
}
static float easing_out_cubic(float t, float b, float c, float d)
{
return c * (pow(t / d - 1, 3) + 1) + b;
}
static float easing_in_out_cubic(float t, float b, float c, float d)
{
t = t / d * 2;
if (t < 1)
return c / 2 * t * t * t + b;
t = t - 2;
return c / 2 * (t * t * t + 2) + b;
}
static float easing_out_in_cubic(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_cubic(t * 2, b, c / 2, d);
return easing_in_cubic((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_in_quart(float t, float b, float c, float d)
{
return c * pow(t / d, 4) + b;
}
static float easing_out_quart(float t, float b, float c, float d)
{
return -c * (pow(t / d - 1, 4) - 1) + b;
}
static float easing_in_out_quart(float t, float b, float c, float d)
{
t = t / d * 2;
if (t < 1)
return c / 2 * pow(t, 4) + b;
return -c / 2 * (pow(t - 2, 4) - 2) + b;
}
static float easing_out_in_quart(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_quart(t * 2, b, c / 2, d);
return easing_in_quart((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_in_quint(float t, float b, float c, float d)
{
return c * pow(t / d, 5) + b;
}
static float easing_out_quint(float t, float b, float c, float d)
{
return c * (pow(t / d - 1, 5) + 1) + b;
}
static float easing_in_out_quint(float t, float b, float c, float d)
{
t = t / d * 2;
if (t < 1)
return c / 2 * pow(t, 5) + b;
return c / 2 * (pow(t - 2, 5) + 2) + b;
}
static float easing_out_in_quint(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_quint(t * 2, b, c / 2, d);
return easing_in_quint((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_in_sine(float t, float b, float c, float d)
{
return -c * cos(t / d * (M_PI / 2)) + c + b;
}
static float easing_out_sine(float t, float b, float c, float d)
{
return c * sin(t / d * (M_PI / 2)) + b;
}
static float easing_in_out_sine(float t, float b, float c, float d)
{
return -c / 2 * (cos(M_PI * t / d) - 1) + b;
}
static float easing_out_in_sine(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_sine(t * 2, b, c / 2, d);
return easing_in_sine((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_in_expo(float t, float b, float c, float d)
{
if (t == 0)
return b;
return c * powf(2, 10 * (t / d - 1)) + b - c * 0.001;
}
static float easing_out_expo(float t, float b, float c, float d)
{
if (t == d)
return b + c;
return c * 1.001 * (-powf(2, -10 * t / d) + 1) + b;
}
static float easing_in_out_expo(float t, float b, float c, float d)
{
if (t == 0)
return b;
if (t == d)
return b + c;
t = t / d * 2;
if (t < 1)
return c / 2 * powf(2, 10 * (t - 1)) + b - c * 0.0005;
return c / 2 * 1.0005 * (-powf(2, -10 * (t - 1)) + 2) + b;
}
static float easing_out_in_expo(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_expo(t * 2, b, c / 2, d);
return easing_in_expo((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_in_circ(float t, float b, float c, float d)
{
return (-c * (sqrt(1 - powf(t / d, 2)) - 1) + b);
}
static float easing_out_circ(float t, float b, float c, float d)
{
return (c * sqrt(1 - powf(t / d - 1, 2)) + b);
}
static float easing_in_out_circ(float t, float b, float c, float d)
{
t = t / d * 2;
if (t < 1)
return -c / 2 * (sqrt(1 - t * t) - 1) + b;
t = t - 2;
return c / 2 * (sqrt(1 - t * t) + 1) + b;
}
static float easing_out_in_circ(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_circ(t * 2, b, c / 2, d);
return easing_in_circ((t * 2) - d, b + c / 2, c / 2, d);
}
static float easing_out_bounce(float t, float b, float c, float d)
{
t = t / d;
if (t < 1 / 2.75)
return c * (7.5625 * t * t) + b;
if (t < 2 / 2.75)
{
t = t - (1.5 / 2.75);
return c * (7.5625 * t * t + 0.75) + b;
}
else if (t < 2.5 / 2.75)
{
t = t - (2.25 / 2.75);
return c * (7.5625 * t * t + 0.9375) + b;
}
t = t - (2.625 / 2.75);
return c * (7.5625 * t * t + 0.984375) + b;
}
static float easing_in_bounce(float t, float b, float c, float d)
{
return c - easing_out_bounce(d - t, 0, c, d) + b;
}
static float easing_in_out_bounce(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_in_bounce(t * 2, 0, c, d) * 0.5 + b;
return easing_out_bounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b;
}
static float easing_out_in_bounce(float t, float b, float c, float d)
{
if (t < d / 2)
return easing_out_bounce(t * 2, b, c / 2, d);
return easing_in_bounce((t * 2) - d, b + c / 2, c / 2, d);
}
static void menu_animation_ticker_generic(uint64_t idx,
size_t max_width, size_t* offset, size_t* width)
{
int ticker_period = (int)(2 * (*width - max_width) + 4);
int phase = idx % ticker_period;
int phase_left_stop = 2;
int phase_left_moving = (int)(phase_left_stop + (*width - max_width));
int phase_right_stop = phase_left_moving + 2;
int left_offset = phase - phase_left_stop;
int right_offset = (int)((*width - max_width) - (phase - phase_right_stop));
if (phase < phase_left_stop)
*offset = 0;
else if (phase < phase_left_moving)
*offset = left_offset;
else if (phase < phase_right_stop)
*offset = *width - max_width;
else
*offset = right_offset;
*width = max_width;
}
static void menu_animation_ticker_loop(uint64_t idx,
size_t max_width, size_t str_width, size_t spacer_width,
size_t* offset1, size_t* width1,
size_t* offset2, size_t* width2,
size_t* offset3, size_t* width3)
{
int ticker_period = (int)(str_width + spacer_width);
int phase = idx % ticker_period;
/* Output offsets/widths are unsigned size_t, but it's
* easier to perform the required calculations with ints,
* so create some temporary variables... */
int offset;
int width;
/* Looping text is composed of up to three strings,
* where std::string 1 and 2 are different regions of the
* source text and std::string 2 is a spacer:
*
* |-----max_width-----|
* [std::string 1][std::string 2][std::string 3]
*
* The following implementation could probably be optimised,
* but any performance gains would be trivial compared with
* all the std::string manipulation that has to happen afterwards...
*/
/* String 1 */
offset = (phase < (int)str_width) ? phase : 0;
width = (int)(str_width - phase);
width = (width < 0) ? 0 : width;
width = (width > (int)max_width) ? max_width : width;
*offset1 = offset;
*width1 = width;
/* String 2 */
offset = (int)(phase - str_width);
offset = offset < 0 ? 0 : offset;
width = (int)(max_width - *width1);
width = (width > (int)spacer_width) ? spacer_width : width;
width = width - offset;
*offset2 = offset;
*width2 = width;
/* String 3 */
width = max_width - (*width1 + *width2);
width = width < 0 ? 0 : width;
/* Note: offset is always zero here so offset3 is
* unnecessary - but include it anyway to preserve
* symmetry... */
*offset3 = 0;
*width3 = width;
}
void menu_animation_init(void)
{
// Nothing to do
}
void menu_animation_free(void)
{
anim.list.clear();
anim.pending.clear();
anim.in_update = false;
anim.pending_deletes = false;
}
static void menu_delayed_animation_cb(void* userdata)
{
menu_delayed_animation_t* delayed_animation = (menu_delayed_animation_t*)userdata;
menu_animation_push(&delayed_animation->entry);
free(delayed_animation);
}
void menu_animation_push_delayed(unsigned delay, menu_animation_ctx_entry_t* entry)
{
menu_timer_ctx_entry_t timer_entry;
menu_delayed_animation_t* delayed_animation = (menu_delayed_animation_t*)malloc(sizeof(menu_delayed_animation_t));
delayed_animation->entry.cb = entry->cb;
delayed_animation->entry.duration = entry->duration;
delayed_animation->entry.easing_enum = entry->easing_enum;
delayed_animation->entry.subject = entry->subject;
delayed_animation->entry.tag = entry->tag;
delayed_animation->entry.target_value = entry->target_value;
delayed_animation->entry.tick = entry->tick;
delayed_animation->entry.userdata = entry->userdata;
timer_entry.cb = menu_delayed_animation_cb;
timer_entry.tick = NULL;
timer_entry.duration = delay;
timer_entry.userdata = delayed_animation;
menu_timer_start(&delayed_animation->timer, &timer_entry);
}
bool menu_animation_push(menu_animation_ctx_entry_t* entry)
{
struct tween t;
t.duration = entry->duration;
t.running_since = 0;
t.initial_value = *entry->subject;
t.target_value = entry->target_value;
t.subject = entry->subject;
t.tag = entry->tag;
t.cb = entry->cb;
t.tick = entry->tick;
t.userdata = entry->userdata;
t.easing = NULL;
t.deleted = false;
switch (entry->easing_enum)
{
case EASING_LINEAR:
t.easing = &easing_linear;
break;
/* Quad */
case EASING_IN_QUAD:
t.easing = &easing_in_quad;
break;
case EASING_OUT_QUAD:
t.easing = &easing_out_quad;
break;
case EASING_IN_OUT_QUAD:
t.easing = &easing_in_out_quad;
break;
case EASING_OUT_IN_QUAD:
t.easing = &easing_out_in_quad;
break;
/* Cubic */
case EASING_IN_CUBIC:
t.easing = &easing_in_cubic;
break;
case EASING_OUT_CUBIC:
t.easing = &easing_out_cubic;
break;
case EASING_IN_OUT_CUBIC:
t.easing = &easing_in_out_cubic;
break;
case EASING_OUT_IN_CUBIC:
t.easing = &easing_out_in_cubic;
break;
/* Quart */
case EASING_IN_QUART:
t.easing = &easing_in_quart;
break;
case EASING_OUT_QUART:
t.easing = &easing_out_quart;
break;
case EASING_IN_OUT_QUART:
t.easing = &easing_in_out_quart;
break;
case EASING_OUT_IN_QUART:
t.easing = &easing_out_in_quart;
break;
/* Quint */
case EASING_IN_QUINT:
t.easing = &easing_in_quint;
break;
case EASING_OUT_QUINT:
t.easing = &easing_out_quint;
break;
case EASING_IN_OUT_QUINT:
t.easing = &easing_in_out_quint;
break;
case EASING_OUT_IN_QUINT:
t.easing = &easing_out_in_quint;
break;
/* Sine */
case EASING_IN_SINE:
t.easing = &easing_in_sine;
break;
case EASING_OUT_SINE:
t.easing = &easing_out_sine;
break;
case EASING_IN_OUT_SINE:
t.easing = &easing_in_out_sine;
break;
case EASING_OUT_IN_SINE:
t.easing = &easing_out_in_sine;
break;
/* Expo */
case EASING_IN_EXPO:
t.easing = &easing_in_expo;
break;
case EASING_OUT_EXPO:
t.easing = &easing_out_expo;
break;
case EASING_IN_OUT_EXPO:
t.easing = &easing_in_out_expo;
break;
case EASING_OUT_IN_EXPO:
t.easing = &easing_out_in_expo;
break;
/* Circ */
case EASING_IN_CIRC:
t.easing = &easing_in_circ;
break;
case EASING_OUT_CIRC:
t.easing = &easing_out_circ;
break;
case EASING_IN_OUT_CIRC:
t.easing = &easing_in_out_circ;
break;
case EASING_OUT_IN_CIRC:
t.easing = &easing_out_in_circ;
break;
/* Bounce */
case EASING_IN_BOUNCE:
t.easing = &easing_in_bounce;
break;
case EASING_OUT_BOUNCE:
t.easing = &easing_out_bounce;
break;
case EASING_IN_OUT_BOUNCE:
t.easing = &easing_in_out_bounce;
break;
case EASING_OUT_IN_BOUNCE:
t.easing = &easing_out_in_bounce;
break;
default:
break;
}
/* ignore born dead tweens */
if (!t.easing || t.duration == 0 || t.initial_value == t.target_value)
return false;
if (anim.in_update)
anim.pending.push_back(t);
else
anim.list.push_back(t);
return true;
}
void menu_animation_get_highlight(float* gradient_x, float* gradient_y, float* color)
{
if (gradient_x)
*gradient_x = (float)highlight_gradient_x;
if (gradient_y)
*gradient_y = (float)highlight_gradient_y;
if (color)
*color = (float)highlight_color;
}
#define HIGHLIGHT_SPEED 350.0
static void menu_animation_update_time(bool timedate_enable)
{
static retro_time_t
last_clock_update
= 0;
static retro_time_t
last_ticker_update
= 0;
static retro_time_t
last_ticker_slow_update
= 0;
/* Adjust ticker speed */
float speed_factor = 1.0f;
unsigned ticker_speed = (unsigned)(((float)TICKER_SPEED / speed_factor) + 0.5);
unsigned ticker_slow_speed = (unsigned)(((float)TICKER_SLOW_SPEED / speed_factor) + 0.5);
cur_time = cpu_features_get_time_usec() / 1000;
delta_time = old_time == 0 ? 0 : cur_time - old_time;
old_time = cur_time;
highlight_gradient_x = (cos((double)cur_time / HIGHLIGHT_SPEED / 3.0) + 1.0) / 2.0;
highlight_gradient_y = (sin((double)cur_time / HIGHLIGHT_SPEED / 3.0) + 1.0) / 2.0;
highlight_color = (sin((double)cur_time / HIGHLIGHT_SPEED * 2.0) + 1.0) / 2.0;
if (((cur_time - last_clock_update) > 1000)
&& timedate_enable)
{
animation_is_active = true;
last_clock_update = cur_time;
}
if (ticker_is_active
&& cur_time - last_ticker_update >= ticker_speed)
{
ticker_idx++;
last_ticker_update = cur_time;
}
if (ticker_is_active
&& cur_time - last_ticker_slow_update >= ticker_slow_speed)
{
ticker_slow_idx++;
last_ticker_slow_update = cur_time;
}
}
bool menu_animation_update(void)
{
unsigned i;
menu_animation_update_time(false);
anim.in_update = true;
anim.pending_deletes = false;
for (i = 0; i < anim.list.size(); i++)
{
struct tween* tween = &anim.list[i];
tween->running_since += delta_time;
*tween->subject = tween->easing(
tween->running_since,
tween->initial_value,
tween->target_value - tween->initial_value,
tween->duration);
if (tween->tick)
tween->tick(tween->userdata);
if (tween->running_since >= tween->duration)
{
*tween->subject = tween->target_value;
if (tween->cb)
tween->cb(tween->userdata);
anim.list.erase(anim.list.begin() + i);
i--;
}
}
if (anim.pending_deletes)
{
for (i = 0; i < anim.list.size(); i++)
{
struct tween* tween = &anim.list[i];
if (tween->deleted)
{
anim.list.erase(anim.list.begin() + i);
i--;
}
}
anim.pending_deletes = false;
}
if (anim.pending.size() > 0)
{
anim.list.insert(anim.list.begin(), anim.pending.begin(), anim.pending.end());
anim.pending.clear();
}
anim.in_update = false;
animation_is_active = anim.list.size() > 0;
return animation_is_active;
}
bool menu_animation_ticker(menu_animation_ctx_ticker_t* ticker)
{
size_t str_len = utf8len(ticker->str);
if (!ticker->spacer)
ticker->spacer = ticker_spacer_default;
if ((size_t)str_len <= ticker->len)
{
utf8cpy(ticker->s,
PATH_MAX_LENGTH,
ticker->str,
ticker->len);
return false;
}
if (!ticker->selected)
{
utf8cpy(ticker->s, PATH_MAX_LENGTH, ticker->str, ticker->len - 3);
strlcat(ticker->s, "...", PATH_MAX_LENGTH);
return false;
}
/* Note: If we reach this point then str_len > ticker->len
* (previously had an unecessary 'if (str_len > ticker->len)'
* check here...) */
switch (ticker->type_enum)
{
case TICKER_TYPE_LOOP:
{
size_t offset1, offset2, offset3;
size_t width1, width2, width3;
/* Horribly oversized temporary buffer
* > utf8 support makes this whole thing incredibly
* ugly/inefficient. Not much we can do about it... */
char tmp[PATH_MAX_LENGTH];
tmp[0] = '\0';
ticker->s[0] = '\0';
menu_animation_ticker_loop(
ticker->idx,
ticker->len,
str_len, utf8len(ticker->spacer),
&offset1, &width1,
&offset2, &width2,
&offset3, &width3);
if (width1 > 0)
{
utf8cpy(
ticker->s,
PATH_MAX_LENGTH,
utf8skip(ticker->str, offset1),
width1);
}
if (width2 > 0)
{
utf8cpy(
tmp,
PATH_MAX_LENGTH,
utf8skip(ticker->spacer, offset2),
width2);
strlcat(ticker->s, tmp, PATH_MAX_LENGTH);
}
if (width3 > 0)
{
utf8cpy(
tmp,
PATH_MAX_LENGTH,
utf8skip(ticker->str, offset3),
width3);
strlcat(ticker->s, tmp, PATH_MAX_LENGTH);
}
break;
}
case TICKER_TYPE_BOUNCE:
default:
{
size_t offset = 0;
menu_animation_ticker_generic(
ticker->idx,
ticker->len,
&offset,
&str_len);
utf8cpy(
ticker->s,
PATH_MAX_LENGTH,
utf8skip(ticker->str, offset),
str_len);
break;
}
}
ticker_is_active = true;
return true;
}
bool menu_animation_is_active(void)
{
return animation_is_active || ticker_is_active;
}
bool menu_animation_kill_by_tag(menu_animation_ctx_tag* tag)
{
unsigned i;
if (!tag || *tag == (uintptr_t)-1)
return false;
for (i = 0; i < anim.list.size(); ++i)
{
struct tween* t = &anim.list[i];
if (t->tag != *tag)
continue;
if (anim.in_update)
{
t->deleted = true;
anim.pending_deletes = true;
}
else
{
anim.list.erase(anim.list.begin() + i);
--i;
}
}
return true;
}
void menu_animation_kill_by_subject(menu_animation_ctx_subject_t* subject)
{
unsigned i, j, killed = 0;
float** sub = (float**)subject->data;
for (i = 0; i < anim.list.size() && killed < subject->count; ++i)
{
struct tween* t = &anim.list[i];
for (j = 0; j < subject->count; ++j)
{
if (t->subject != sub[j])
continue;
if (anim.in_update)
{
t->deleted = true;
anim.pending_deletes = true;
}
else
{
anim.list.erase(anim.list.begin() + i);
--i;
}
killed++;
break;
}
}
}
float menu_animation_get_delta_time(void)
{
return delta_time;
}
bool menu_animation_ctl(enum menu_animation_ctl_state state, void* data)
{
switch (state)
{
case MENU_ANIMATION_CTL_CLEAR_ACTIVE:
animation_is_active = false;
ticker_is_active = false;
break;
case MENU_ANIMATION_CTL_SET_ACTIVE:
animation_is_active = true;
ticker_is_active = true;
break;
case MENU_ANIMATION_CTL_NONE:
default:
break;
}
return true;
}
void menu_timer_start(menu_timer_t* timer, menu_timer_ctx_entry_t* timer_entry)
{
menu_animation_ctx_entry_t entry;
menu_animation_ctx_tag tag = (uintptr_t)timer;
menu_timer_kill(timer);
*timer = 0.0f;
entry.easing_enum = EASING_LINEAR;
entry.tag = tag;
entry.duration = timer_entry->duration;
entry.target_value = 1.0f;
entry.subject = timer;
entry.cb = timer_entry->cb;
entry.tick = timer_entry->tick;
entry.userdata = timer_entry->userdata;
menu_animation_push(&entry);
}
void menu_timer_kill(menu_timer_t* timer)
{
menu_animation_ctx_tag tag = (uintptr_t)timer;
menu_animation_kill_by_tag(&tag);
}
uint64_t menu_animation_get_ticker_idx(void)
{
return ticker_idx;
}
uint64_t menu_animation_get_ticker_slow_idx(void)
{
return ticker_slow_idx;
}
} // namespace brls

View File

@@ -0,0 +1,414 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/applet_frame.hpp>
#include <borealis/application.hpp>
namespace brls
{
AppletFrame::AppletFrame(bool padLeft, bool padRight)
{
Style* style = Application::getStyle();
if (padLeft)
this->leftPadding = style->AppletFrame.separatorSpacing;
if (padRight)
this->rightPadding = style->AppletFrame.separatorSpacing;
this->hint = new Hint();
this->hint->setParent(this);
this->registerAction("Back", Key::B, [this] { return this->onCancel(); });
}
void AppletFrame::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
// Text
if (this->headerStyle == HeaderStyle::REGULAR)
{
// Title
NVGcolor titleColor = a(ctx->theme->textColor);
if (this->contentView)
titleColor.a *= this->contentView->getAlpha();
nvgFillColor(vg, titleColor);
nvgFontSize(vg, style->AppletFrame.titleSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgBeginPath(vg);
nvgText(vg, x + style->AppletFrame.titleStart, y + style->AppletFrame.headerHeightRegular / 2 + style->AppletFrame.titleOffset, this->title.c_str(), nullptr);
// Header
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->textColor));
nvgRect(vg, x + style->AppletFrame.separatorSpacing, y + style->AppletFrame.headerHeightRegular - 1, width - style->AppletFrame.separatorSpacing * 2, 1);
nvgFill(vg);
}
else if (this->headerStyle == HeaderStyle::POPUP)
{
// Header Text
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->textColor));
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, style->PopupFrame.headerFontSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgText(vg, x + style->PopupFrame.headerTextLeftPadding,
y + style->PopupFrame.headerTextTopPadding,
this->title.c_str(),
nullptr);
// Sub title text 1
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->descriptionColor));
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, style->PopupFrame.subTitleFontSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
nvgText(vg, x + style->PopupFrame.subTitleLeftPadding,
y + style->PopupFrame.subTitleTopPadding,
this->subTitleLeft.c_str(),
nullptr);
float bounds[4];
nvgTextBounds(vg, x, y, this->subTitleLeft.c_str(), nullptr, bounds);
// Sub title separator
nvgFillColor(vg, a(ctx->theme->descriptionColor)); // we purposely don't apply opacity
nvgBeginPath(vg);
nvgRect(vg, x + style->PopupFrame.subTitleLeftPadding + (bounds[2] - bounds[0]) + style->PopupFrame.subTitleSpacing,
y + style->PopupFrame.subTitleSeparatorTopPadding,
1,
style->PopupFrame.subTitleSeparatorHeight);
nvgFill(vg);
// Sub title text 2
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->descriptionColor));
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, style->PopupFrame.subTitleFontSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
nvgText(vg, x + style->PopupFrame.subTitleLeftPadding + (bounds[2] - bounds[0]) + (style->PopupFrame.subTitleSpacing * 2),
y + style->PopupFrame.subTitleTopPadding,
this->subTitleRight.c_str(),
nullptr);
// Header
nvgBeginPath(vg);
nvgRect(vg, x + style->AppletFrame.separatorSpacing, y + style->AppletFrame.headerHeightPopup - 1, width - style->AppletFrame.separatorSpacing * 2, 1);
nvgFill(vg);
}
// Footer
NVGcolor footerColor = a(ctx->theme->textColor);
if (this->slideIn)
footerColor.a = 0.0f;
else if (this->slideOut)
footerColor.a = 1.0f;
nvgFillColor(vg, footerColor);
std::string* text = &this->footerText;
if (*text == "")
text = Application::getCommonFooter();
nvgFontSize(vg, style->AppletFrame.footerTextSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + style->AppletFrame.separatorSpacing + style->AppletFrame.footerTextSpacing, y + height - style->AppletFrame.footerHeight / 2, text->c_str(), nullptr);
// Hint
this->hint->frame(ctx);
// Icon
if (this->icon)
this->icon->frame(ctx);
// Separators
nvgFillColor(vg, a(ctx->theme->separatorColor));
// Footer
nvgBeginPath(vg);
nvgRect(vg, x + style->AppletFrame.separatorSpacing, y + height - style->AppletFrame.footerHeight, width - style->AppletFrame.separatorSpacing * 2, 1);
nvgFill(vg);
// Content view
if (contentView)
{
float slideAlpha = 1.0f - this->contentView->alpha;
if ((this->slideIn && this->animation == ViewAnimation::SLIDE_LEFT) || (this->slideOut && this->animation == ViewAnimation::SLIDE_RIGHT))
slideAlpha = 1.0f - slideAlpha;
int translation = (int)((float)style->AppletFrame.slideAnimation * slideAlpha);
if ((this->slideIn && this->animation == ViewAnimation::SLIDE_LEFT) || (this->slideOut && this->animation == ViewAnimation::SLIDE_RIGHT))
translation -= style->AppletFrame.slideAnimation;
if (this->slideOut || this->slideIn)
nvgTranslate(vg, -translation, 0);
contentView->frame(ctx);
if (this->slideOut || this->slideIn)
nvgTranslate(vg, translation, 0);
}
}
View* AppletFrame::getDefaultFocus()
{
if (this->contentView)
return this->contentView->getDefaultFocus();
return nullptr;
}
void AppletFrame::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Icon
if (this->icon)
{
if (this->headerStyle == HeaderStyle::REGULAR)
{
this->icon->setBoundaries(style->AppletFrame.imageLeftPadding, style->AppletFrame.imageTopPadding, style->AppletFrame.imageSize, style->AppletFrame.imageSize);
this->icon->invalidate();
}
else if (this->headerStyle == HeaderStyle::POPUP)
{
this->icon->setBoundaries(style->PopupFrame.edgePadding + style->PopupFrame.imageLeftPadding, style->PopupFrame.imageTopPadding, style->PopupFrame.imageSize, style->PopupFrame.imageSize);
this->icon->invalidate();
}
}
// Content
if (this->contentView)
{
if (this->headerStyle == HeaderStyle::REGULAR)
this->contentView->setBoundaries(this->x + leftPadding, this->y + style->AppletFrame.headerHeightRegular, this->width - this->leftPadding - this->rightPadding, this->height - style->AppletFrame.footerHeight - style->AppletFrame.headerHeightRegular);
else if (this->headerStyle == HeaderStyle::POPUP)
this->contentView->setBoundaries(this->x + leftPadding, this->y + style->AppletFrame.headerHeightPopup, this->width - this->leftPadding - this->rightPadding, this->height - style->AppletFrame.footerHeight - style->AppletFrame.headerHeightPopup);
this->contentView->invalidate();
}
// Hint
// TODO: convert the bottom-left footer into a Label to get its width and avoid clipping with the hint
unsigned hintWidth = this->width - style->AppletFrame.separatorSpacing * 2 - style->AppletFrame.footerTextSpacing * 2;
this->hint->setBoundaries(
this->x + this->width - hintWidth - style->AppletFrame.separatorSpacing - style->AppletFrame.footerTextSpacing,
this->y + this->height - style->AppletFrame.footerHeight,
hintWidth,
style->AppletFrame.footerHeight);
this->hint->invalidate();
}
void AppletFrame::setContentView(View* view)
{
this->contentView = view;
if (this->contentView)
{
this->contentView->setParent(this);
this->contentView->willAppear();
}
this->invalidate();
}
bool AppletFrame::hasContentView()
{
return this->contentView;
}
void AppletFrame::setTitle(std::string title)
{
this->title = title;
}
void AppletFrame::setFooterText(std::string footerText)
{
this->footerText = footerText;
}
void AppletFrame::setSubtitle(std::string left, std::string right)
{
this->subTitleLeft = left;
this->subTitleRight = right;
}
void AppletFrame::setIcon(unsigned char* buffer, size_t bufferSize)
{
if (!this->icon)
{
Image* icon = new Image(buffer, bufferSize);
icon->setScaleType(ImageScaleType::SCALE);
icon->setParent(this);
this->icon = icon;
}
else if (Image* icon = dynamic_cast<Image*>(this->icon))
{
icon->setImage(buffer, bufferSize);
}
this->icon->invalidate();
}
void AppletFrame::setIcon(std::string imagePath)
{
if (!this->icon)
{
Image* icon = new Image(imagePath);
icon->setScaleType(ImageScaleType::SCALE);
icon->setParent(this);
this->icon = icon;
}
else if (Image* icon = dynamic_cast<Image*>(this->icon))
{
icon->setImage(imagePath);
}
this->icon->invalidate();
}
void AppletFrame::setIcon(View* view)
{
if (this->icon)
delete this->icon;
if (view != nullptr)
view->setParent(this);
this->icon = view;
}
void AppletFrame::setHeaderStyle(HeaderStyle headerStyle)
{
this->headerStyle = headerStyle;
this->invalidate();
}
AppletFrame::~AppletFrame()
{
if (this->contentView)
{
this->contentView->willDisappear(true);
delete this->contentView;
}
if (this->icon)
delete this->icon;
delete this->hint;
}
void AppletFrame::willAppear(bool resetState)
{
if (this->icon)
this->icon->willAppear(resetState);
if (this->contentView)
this->contentView->willAppear(resetState);
this->hint->willAppear(resetState);
}
void AppletFrame::willDisappear(bool resetState)
{
if (this->icon)
this->icon->willDisappear(resetState);
if (this->contentView)
this->contentView->willDisappear(resetState);
this->hint->willDisappear(resetState);
}
void AppletFrame::show(std::function<void(void)> cb, bool animated, ViewAnimation animation)
{
this->animation = animation;
if (animated && (animation == ViewAnimation::SLIDE_LEFT || animation == ViewAnimation::SLIDE_RIGHT) && this->contentView)
{
this->slideIn = true;
this->contentView->show([this]() {
this->slideIn = false;
},
true, animation);
}
else if (this->contentView && this->contentView->isHidden())
{
this->contentView->show([]() {}, animated, animation);
}
View::show(cb, animated, animation);
}
void AppletFrame::hide(std::function<void(void)> cb, bool animated, ViewAnimation animation)
{
this->animation = animation;
if (animated && (animation == ViewAnimation::SLIDE_LEFT || animation == ViewAnimation::SLIDE_RIGHT) && this->contentView)
{
this->slideOut = true;
this->contentView->hide([this, cb]() {
this->slideOut = false;
},
true, animation);
}
else if (this->contentView && !this->contentView->isHidden())
{
this->contentView->hide([]() {}, animated, animation);
}
View::hide(cb, animated, animation);
}
bool AppletFrame::onCancel()
{
/*
* TODO: this assumes AppletFrames are used as "activities" in the app, which may be wrong
* so we should instead change the view stack to an "activity" stack and have them popped when
* the user presses B on the root view of an "activity"
*/
Application::popView();
return true;
}
void AppletFrame::onWindowSizeChanged()
{
if (this->contentView)
this->contentView->onWindowSizeChanged();
if (this->icon)
this->icon->onWindowSizeChanged();
if (this->hint)
this->hint->onWindowSizeChanged();
}
} // namespace brls

View File

@@ -0,0 +1,991 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
Copyright (C) 2020 WerWolv
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <algorithm>
#include <borealis.hpp>
#include <string>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad.h>
#define GLM_FORCE_PURE
#define GLM_ENABLE_EXPERIMENTAL
#include <nanovg.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#define NANOVG_GL3_IMPLEMENTATION
#include <nanovg_gl.h>
#ifdef __SWITCH__
#include <switch.h>
#endif
#include <chrono>
#include <set>
#include <thread>
// Constants used for scaling as well as
// creating a window of the right size on PC
constexpr uint32_t WINDOW_WIDTH = 1280;
constexpr uint32_t WINDOW_HEIGHT = 720;
#define DEFAULT_FPS 60
#define BUTTON_REPEAT_DELAY 15
#define BUTTON_REPEAT_CADENCY 5
// glfw code from the glfw hybrid app by fincs
// https://github.com/fincs/hybrid_app
namespace brls
{
// TODO: Use this instead of a glViewport each frame
static void windowFramebufferSizeCallback(GLFWwindow* window, int width, int height)
{
if (!width || !height)
return;
glViewport(0, 0, width, height);
Application::windowScale = (float)width / (float)WINDOW_WIDTH;
float contentHeight = ((float)height / (Application::windowScale * (float)WINDOW_HEIGHT)) * (float)WINDOW_HEIGHT;
Application::contentWidth = WINDOW_WIDTH;
Application::contentHeight = (unsigned)roundf(contentHeight);
Application::resizeNotificationManager();
Logger::info("Window size changed to %dx%d", width, height);
Logger::info("New scale factor is %f", Application::windowScale);
}
static void joystickCallback(int jid, int event)
{
if (event == GLFW_CONNECTED)
{
Logger::info("Joystick %d connected", jid);
if (glfwJoystickIsGamepad(jid))
Logger::info("Joystick %d is gamepad: \"%s\"", jid, glfwGetGamepadName(jid));
}
else if (event == GLFW_DISCONNECTED)
Logger::info("Joystick %d disconnected", jid);
}
static void errorCallback(int errorCode, const char* description)
{
Logger::error("[GLFW:%d] %s", errorCode, description);
}
static void windowKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS)
{
// Check for toggle-fullscreen combo
if (key == GLFW_KEY_ENTER && mods == GLFW_MOD_ALT)
{
static int saved_x, saved_y, saved_width, saved_height;
if (!glfwGetWindowMonitor(window))
{
// Back up window position/size
glfwGetWindowPos(window, &saved_x, &saved_y);
glfwGetWindowSize(window, &saved_width, &saved_height);
// Switch to fullscreen mode
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
}
else
{
// Switch back to windowed mode
glfwSetWindowMonitor(window, nullptr, saved_x, saved_y, saved_width, saved_height, GLFW_DONT_CARE);
}
}
}
}
bool Application::init(std::string title)
{
return Application::init(title, Style::horizon(), Theme::horizon());
}
bool Application::init(std::string title, Style style, Theme theme)
{
// Init rng
std::srand(std::time(nullptr));
// Init managers
Application::taskManager = new TaskManager();
Application::notificationManager = new NotificationManager();
// Init static variables
Application::currentStyle = style;
Application::currentFocus = nullptr;
Application::oldGamepad = {};
Application::gamepad = {};
Application::title = title;
// Init theme to defaults
Application::setTheme(theme);
// Init glfw
glfwSetErrorCallback(errorCallback);
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
if (!glfwInit())
{
Logger::error("Failed to initialize glfw");
return false;
}
// Create window
#ifdef __APPLE__
// Explicitly ask for a 3.2 context on OS X
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Force scaling off to keep desired framebuffer size
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#endif
Application::window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, title.c_str(), nullptr, nullptr);
if (!window)
{
Logger::error("glfw: failed to create window\n");
glfwTerminate();
return false;
}
// Configure window
glfwSetInputMode(window, GLFW_STICKY_KEYS, GLFW_TRUE);
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, windowFramebufferSizeCallback);
glfwSetKeyCallback(window, windowKeyCallback);
glfwSetJoystickCallback(joystickCallback);
// Load OpenGL routines using glad
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
glfwSwapInterval(1);
Logger::info("GL Vendor: %s", glGetString(GL_VENDOR));
Logger::info("GL Renderer: %s", glGetString(GL_RENDERER));
Logger::info("GL Version: %s", glGetString(GL_VERSION));
if (glfwJoystickIsGamepad(GLFW_JOYSTICK_1))
{
GLFWgamepadstate state;
Logger::info("Gamepad detected: %s", glfwGetGamepadName(GLFW_JOYSTICK_1));
glfwGetGamepadState(GLFW_JOYSTICK_1, &state);
}
// Initialize the scene
Application::vg = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_ANTIALIAS);
if (!vg)
{
Logger::error("Unable to init nanovg");
glfwTerminate();
return false;
}
windowFramebufferSizeCallback(window, WINDOW_WIDTH, WINDOW_HEIGHT);
glfwSetTime(0.0);
// Load fonts
#ifdef __SWITCH__
{
PlFontData font;
// Standard font
Result rc = plGetSharedFontByType(&font, PlSharedFontType_Standard);
if (R_SUCCEEDED(rc))
{
Logger::info("Using Switch shared font");
Application::fontStash.regular = Application::loadFontFromMemory("regular", font.address, font.size, false);
}
// Korean font
rc = plGetSharedFontByType(&font, PlSharedFontType_KO);
if (R_SUCCEEDED(rc))
{
Logger::info("Adding Switch shared Korean font");
Application::fontStash.korean = Application::loadFontFromMemory("korean", font.address, font.size, false);
nvgAddFallbackFontId(Application::vg, Application::fontStash.regular, Application::fontStash.korean);
}
// Extented font
rc = plGetSharedFontByType(&font, PlSharedFontType_NintendoExt);
if (R_SUCCEEDED(rc))
{
Logger::info("Using Switch shared symbols font");
Application::fontStash.sharedSymbols = Application::loadFontFromMemory("symbols", font.address, font.size, false);
}
}
#else
// Use illegal font if available
if (access(BOREALIS_ASSET("Illegal-Font.ttf"), F_OK) != -1)
Application::fontStash.regular = Application::loadFont("regular", BOREALIS_ASSET("Illegal-Font.ttf"));
else
Application::fontStash.regular = Application::loadFont("regular", BOREALIS_ASSET("inter/Inter-Switch.ttf"));
if (access(BOREALIS_ASSET("Wingdings.ttf"), F_OK) != -1)
Application::fontStash.sharedSymbols = Application::loadFont("sharedSymbols", BOREALIS_ASSET("Wingdings.ttf"));
#endif
// Material font
if (access(BOREALIS_ASSET("material/MaterialIcons-Regular.ttf"), F_OK) != -1)
Application::fontStash.material = Application::loadFont("material", BOREALIS_ASSET("material/MaterialIcons-Regular.ttf"));
// Set symbols font as fallback
if (Application::fontStash.sharedSymbols)
{
Logger::info("Using shared symbols font");
nvgAddFallbackFontId(Application::vg, Application::fontStash.regular, Application::fontStash.sharedSymbols);
}
else
{
Logger::error("Shared symbols font not found");
}
// Set Material as fallback
if (Application::fontStash.material)
{
Logger::info("Using Material font");
nvgAddFallbackFontId(Application::vg, Application::fontStash.regular, Application::fontStash.material);
}
else
{
Logger::error("Material font not found");
}
// Load theme
#ifdef __SWITCH__
ColorSetId nxTheme;
setsysGetColorSetId(&nxTheme);
if (nxTheme == ColorSetId_Dark)
Application::currentThemeVariant = ThemeVariant_DARK;
else
Application::currentThemeVariant = ThemeVariant_LIGHT;
#else
char* themeEnv = getenv("BOREALIS_THEME");
if (themeEnv != nullptr && !strcasecmp(themeEnv, "DARK"))
Application::currentThemeVariant = ThemeVariant_DARK;
else
Application::currentThemeVariant = ThemeVariant_LIGHT;
#endif
// Init window size
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
Application::windowWidth = viewport[2];
Application::windowHeight = viewport[3];
// Init animations engine
menu_animation_init();
// Default FPS cap
Application::setMaximumFPS(DEFAULT_FPS);
return true;
}
bool Application::mainLoop()
{
// Frame start
retro_time_t frameStart = 0;
if (Application::frameTime > 0.0f)
frameStart = cpu_features_get_time_usec();
// glfw events
bool is_active;
do
{
is_active = !glfwGetWindowAttrib(Application::window, GLFW_ICONIFIED);
if (is_active)
glfwPollEvents();
else
glfwWaitEvents();
if (glfwWindowShouldClose(Application::window))
{
Application::exit();
return false;
}
} while (!is_active);
// libnx applet main loop
#ifdef __SWITCH__
if (!appletMainLoop())
{
Application::exit();
return false;
}
#endif
// Gamepad
if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &Application::gamepad))
{
// Keyboard -> DPAD Mapping
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT] = glfwGetKey(window, GLFW_KEY_LEFT);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] = glfwGetKey(window, GLFW_KEY_RIGHT);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP] = glfwGetKey(window, GLFW_KEY_UP);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN] = glfwGetKey(window, GLFW_KEY_DOWN);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_START] = glfwGetKey(window, GLFW_KEY_ESCAPE);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_BACK] = glfwGetKey(window, GLFW_KEY_F1);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_A] = glfwGetKey(window, GLFW_KEY_ENTER);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_B] = glfwGetKey(window, GLFW_KEY_BACKSPACE);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] = glfwGetKey(window, GLFW_KEY_L);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] = glfwGetKey(window, GLFW_KEY_R);
}
// Trigger gamepad events
// TODO: Translate axis events to dpad events here
bool anyButtonPressed = false;
bool repeating = false;
static retro_time_t buttonPressTime = 0;
static int repeatingButtonTimer = 0;
for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++)
{
if (Application::gamepad.buttons[i] == GLFW_PRESS)
{
anyButtonPressed = true;
repeating = (repeatingButtonTimer > BUTTON_REPEAT_DELAY && repeatingButtonTimer % BUTTON_REPEAT_CADENCY == 0);
if (Application::oldGamepad.buttons[i] != GLFW_PRESS || repeating)
Application::onGamepadButtonPressed(i, repeating);
}
if (Application::gamepad.buttons[i] != Application::oldGamepad.buttons[i])
buttonPressTime = repeatingButtonTimer = 0;
}
if (anyButtonPressed && cpu_features_get_time_usec() - buttonPressTime > 1000)
{
buttonPressTime = cpu_features_get_time_usec();
repeatingButtonTimer++; // Increased once every ~1ms
}
Application::oldGamepad = Application::gamepad;
// Handle window size changes
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
unsigned newWidth = viewport[2];
unsigned newHeight = viewport[3];
if (Application::windowWidth != newWidth || Application::windowHeight != newHeight)
{
Application::windowWidth = newWidth;
Application::windowHeight = newHeight;
Application::onWindowSizeChanged();
}
// Animations
menu_animation_update();
// Tasks
Application::taskManager->frame();
// Render
Application::frame();
glfwSwapBuffers(window);
// Sleep if necessary
if (Application::frameTime > 0.0f)
{
retro_time_t currentFrameTime = cpu_features_get_time_usec() - frameStart;
retro_time_t frameTime = (retro_time_t)(Application::frameTime * 1000);
if (frameTime > currentFrameTime)
{
retro_time_t toSleep = frameTime - currentFrameTime;
std::this_thread::sleep_for(std::chrono::microseconds(toSleep));
}
}
return true;
}
void Application::quit()
{
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
void Application::navigate(FocusDirection direction)
{
View* currentFocus = Application::currentFocus;
// Do nothing if there is no current focus or if it doesn't have a parent
// (in which case there is nothing to traverse)
if (!currentFocus || !currentFocus->hasParent())
return;
// Get next view to focus by traversing the views tree upwards
View* nextFocus = currentFocus->getParent()->getNextFocus(direction, currentFocus->getParentUserData());
while (!nextFocus) // stop when we find a view to focus
{
if (!currentFocus->hasParent() || !currentFocus->getParent()->hasParent()) // stop when we reach the root of the tree
break;
currentFocus = currentFocus->getParent();
nextFocus = currentFocus->getParent()->getNextFocus(direction, currentFocus->getParentUserData());
}
// No view to focus at the end of the traversal: wiggle and return
if (!nextFocus)
{
Application::currentFocus->shakeHighlight(direction);
return;
}
// Otherwise give it focus
Application::giveFocus(nextFocus);
}
void Application::onGamepadButtonPressed(char button, bool repeating)
{
if (Application::blockInputsTokens != 0)
return;
if (repeating && Application::repetitionOldFocus == Application::currentFocus)
return;
Application::repetitionOldFocus = Application::currentFocus;
// Actions
if (Application::handleAction(button))
return;
// Navigation
// Only navigate if the button hasn't been consumed by an action
// (allows overriding DPAD buttons using actions)
switch (button)
{
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
Application::navigate(FocusDirection::DOWN);
break;
case GLFW_GAMEPAD_BUTTON_DPAD_UP:
Application::navigate(FocusDirection::UP);
break;
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT:
Application::navigate(FocusDirection::LEFT);
break;
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
Application::navigate(FocusDirection::RIGHT);
break;
default:
break;
}
}
View* Application::getCurrentFocus()
{
return Application::currentFocus;
}
bool Application::handleAction(char button)
{
View* hintParent = Application::currentFocus;
std::set<Key> consumedKeys;
while (hintParent != nullptr)
{
for (auto& action : hintParent->getActions())
{
if (action.key != static_cast<Key>(button))
continue;
if (consumedKeys.find(action.key) != consumedKeys.end())
continue;
if (action.available)
if (action.actionListener())
consumedKeys.insert(action.key);
}
hintParent = hintParent->getParent();
}
return !consumedKeys.empty();
}
void Application::frame()
{
// Frame context
FrameContext frameContext = FrameContext();
frameContext.pixelRatio = (float)Application::windowWidth / (float)Application::windowHeight;
frameContext.vg = Application::vg;
frameContext.fontStash = &Application::fontStash;
frameContext.theme = Application::getThemeValues();
nvgBeginFrame(Application::vg, Application::windowWidth, Application::windowHeight, frameContext.pixelRatio);
nvgScale(Application::vg, Application::windowScale, Application::windowScale);
// GL Clear
glClearColor(
frameContext.theme->backgroundColor[0],
frameContext.theme->backgroundColor[1],
frameContext.theme->backgroundColor[2],
1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
std::vector<View*> viewsToDraw;
// Draw all views in the stack
// until we find one that's not translucent
for (size_t i = 0; i < Application::viewStack.size(); i++)
{
View* view = Application::viewStack[Application::viewStack.size() - 1 - i];
viewsToDraw.push_back(view);
if (!view->isTranslucent())
break;
}
for (size_t i = 0; i < viewsToDraw.size(); i++)
{
View* view = viewsToDraw[viewsToDraw.size() - 1 - i];
view->frame(&frameContext);
}
// Framerate counter
if (Application::framerateCounter)
Application::framerateCounter->frame(&frameContext);
// Notifications
Application::notificationManager->frame(&frameContext);
// End frame
nvgResetTransform(Application::vg); // scale
nvgEndFrame(Application::vg);
}
void Application::exit()
{
Application::clear();
if (Application::vg)
nvgDeleteGL3(Application::vg);
glfwTerminate();
menu_animation_free();
if (Application::framerateCounter)
delete Application::framerateCounter;
delete Application::taskManager;
delete Application::notificationManager;
}
void Application::setDisplayFramerate(bool enabled)
{
if (!Application::framerateCounter && enabled)
{
Logger::info("Enabling framerate counter");
Application::framerateCounter = new FramerateCounter();
Application::resizeFramerateCounter();
}
else if (Application::framerateCounter && !enabled)
{
Logger::info("Disabling framerate counter");
delete Application::framerateCounter;
Application::framerateCounter = nullptr;
}
}
void Application::toggleFramerateDisplay()
{
Application::setDisplayFramerate(!Application::framerateCounter);
}
void Application::resizeFramerateCounter()
{
if (!Application::framerateCounter)
return;
Style* style = Application::getStyle();
unsigned framerateCounterWidth = style->FramerateCounter.width;
unsigned width = WINDOW_WIDTH;
Application::framerateCounter->setBoundaries(
width - framerateCounterWidth,
0,
framerateCounterWidth,
style->FramerateCounter.height);
Application::framerateCounter->invalidate();
}
void Application::resizeNotificationManager()
{
Application::notificationManager->setBoundaries(0, 0, Application::contentWidth, Application::contentHeight);
Application::notificationManager->invalidate();
}
void Application::notify(std::string text)
{
Application::notificationManager->notify(text);
}
NotificationManager* Application::getNotificationManager()
{
return Application::notificationManager;
}
void Application::giveFocus(View* view)
{
View* oldFocus = Application::currentFocus;
View* newFocus = view ? view->getDefaultFocus() : nullptr;
if (oldFocus != newFocus)
{
if (oldFocus)
oldFocus->onFocusLost();
Application::currentFocus = newFocus;
Application::globalFocusChangeEvent.fire(newFocus);
if (newFocus)
{
newFocus->onFocusGained();
Logger::debug("Giving focus to %s", newFocus->describe().c_str());
}
}
}
void Application::popView(ViewAnimation animation, std::function<void(void)> cb)
{
if (Application::viewStack.size() <= 1) // never pop the root view
return;
Application::blockInputs();
View* last = Application::viewStack[Application::viewStack.size() - 1];
last->willDisappear(true);
last->setForceTranslucent(true);
bool wait = animation == ViewAnimation::FADE; // wait for the new view animation to be done before showing the old one?
// Hide animation (and show previous view, if any)
last->hide([last, animation, wait, cb]() {
last->setForceTranslucent(false);
Application::viewStack.pop_back();
delete last;
// Animate the old view once the new one
// has ended its animation
if (Application::viewStack.size() > 0 && wait)
{
View* newLast = Application::viewStack[Application::viewStack.size() - 1];
if (newLast->isHidden())
{
newLast->willAppear(false);
newLast->show(cb, true, animation);
}
else
{
cb();
}
}
Application::unblockInputs();
},
true, animation);
// Animate the old view immediately
if (!wait && Application::viewStack.size() > 1)
{
View* toShow = Application::viewStack[Application::viewStack.size() - 2];
toShow->willAppear(false);
toShow->show(cb, true, animation);
}
// Focus
if (Application::focusStack.size() > 0)
{
View* newFocus = Application::focusStack[Application::focusStack.size() - 1];
Logger::debug("Giving focus to %s, and removing it from the focus stack", newFocus->describe().c_str());
Application::giveFocus(newFocus);
Application::focusStack.pop_back();
}
}
void Application::pushView(View* view, ViewAnimation animation)
{
Application::blockInputs();
// Call hide() on the previous view in the stack if no
// views are translucent, then call show() once the animation ends
View* last = nullptr;
if (Application::viewStack.size() > 0)
last = Application::viewStack[Application::viewStack.size() - 1];
bool fadeOut = last && !last->isTranslucent() && !view->isTranslucent(); // play the fade out animation?
bool wait = animation == ViewAnimation::FADE; // wait for the old view animation to be done before showing the new one?
view->registerAction("Exit", Key::PLUS, [] { Application::quit(); return true; });
view->registerAction(
"FPS", Key::MINUS, [] { Application::toggleFramerateDisplay(); return true; }, true);
// Fade out animation
if (fadeOut)
{
view->setForceTranslucent(true); // set the new view translucent until the fade out animation is done playing
// Animate the new view directly
if (!wait)
{
view->show([]() {
Application::unblockInputs();
},
true, animation);
}
last->hide([animation, wait]() {
View* newLast = Application::viewStack[Application::viewStack.size() - 1];
newLast->setForceTranslucent(false);
// Animate the new view once the old one
// has ended its animation
if (wait)
newLast->show([]() { Application::unblockInputs(); }, true, animation);
},
true, animation);
}
view->setBoundaries(0, 0, Application::contentWidth, Application::contentHeight);
if (!fadeOut)
view->show([]() { Application::unblockInputs(); }, true, animation);
else
view->alpha = 0.0f;
// Focus
if (Application::viewStack.size() > 0)
{
Logger::debug("Pushing %s to the focus stack", Application::currentFocus->describe().c_str());
Application::focusStack.push_back(Application::currentFocus);
}
// Layout and prepare view
view->invalidate(true);
view->willAppear(true);
Application::giveFocus(view->getDefaultFocus());
// And push it
Application::viewStack.push_back(view);
}
void Application::onWindowSizeChanged()
{
Logger::debug("Layout triggered");
for (View* view : Application::viewStack)
{
view->setBoundaries(0, 0, Application::contentWidth, Application::contentHeight);
view->invalidate();
view->onWindowSizeChanged();
}
Application::resizeNotificationManager();
Application::resizeFramerateCounter();
}
void Application::clear()
{
for (View* view : Application::viewStack)
{
view->willDisappear(true);
delete view;
}
Application::viewStack.clear();
}
Style* Application::getStyle()
{
return &Application::currentStyle;
}
void Application::setTheme(Theme theme)
{
Application::currentTheme = theme;
}
ThemeValues* Application::getThemeValues()
{
return &Application::currentTheme.colors[Application::currentThemeVariant];
}
ThemeValues* Application::getThemeValuesForVariant(ThemeVariant variant)
{
return &Application::currentTheme.colors[variant];
}
ThemeVariant Application::getThemeVariant()
{
return Application::currentThemeVariant;
}
int Application::loadFont(const char* fontName, const char* filePath)
{
return nvgCreateFont(Application::vg, fontName, filePath);
}
int Application::loadFontFromMemory(const char* fontName, void* address, size_t size, bool freeData)
{
return nvgCreateFontMem(Application::vg, fontName, (unsigned char*)address, size, freeData);
}
int Application::findFont(const char* fontName)
{
return nvgFindFont(Application::vg, fontName);
}
void Application::crash(std::string text)
{
CrashFrame* crashFrame = new CrashFrame(text);
Application::pushView(crashFrame);
}
void Application::blockInputs()
{
Application::blockInputsTokens += 1;
}
void Application::unblockInputs()
{
if (Application::blockInputsTokens > 0)
Application::blockInputsTokens -= 1;
}
NVGcontext* Application::getNVGContext()
{
return Application::vg;
}
TaskManager* Application::getTaskManager()
{
return Application::taskManager;
}
void Application::setCommonFooter(std::string footer)
{
Application::commonFooter = footer;
}
std::string* Application::getCommonFooter()
{
return &Application::commonFooter;
}
FramerateCounter::FramerateCounter()
: Label(LabelStyle::LIST_ITEM, "FPS: ---")
{
this->setColor(nvgRGB(255, 255, 255));
this->setVerticalAlign(NVG_ALIGN_MIDDLE);
this->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->setBackground(Background::BACKDROP);
this->lastSecond = cpu_features_get_time_usec() / 1000;
}
void FramerateCounter::frame(FrameContext* ctx)
{
// Update counter
retro_time_t current = cpu_features_get_time_usec() / 1000;
if (current - this->lastSecond >= 1000)
{
char fps[10];
snprintf(fps, sizeof(fps), "FPS: %03d", this->frames);
this->setText(std::string(fps));
this->invalidate(); // update width for background
this->frames = 0;
this->lastSecond = current;
}
this->frames++;
// Regular frame
Label::frame(ctx);
}
void Application::setMaximumFPS(unsigned fps)
{
if (fps == 0)
Application::frameTime = 0.0f;
else
{
Application::frameTime = 1000 / (float)fps;
}
Logger::info("Maximum FPS set to %d - using a frame time of %.2f ms", fps, Application::frameTime);
}
std::string Application::getTitle()
{
return Application::title;
}
GenericEvent* Application::getGlobalFocusChangeEvent()
{
return &Application::globalFocusChangeEvent;
}
VoidEvent* Application::getGlobalHintsUpdateEvent()
{
return &Application::globalHintsUpdateEvent;
}
FontStash* Application::getFontStash()
{
return &Application::fontStash;
}
} // namespace brls

View File

@@ -0,0 +1,376 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdio.h>
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/box_layout.hpp>
#include <borealis/logger.hpp>
#include <iterator>
namespace brls
{
BoxLayout::BoxLayout(BoxLayoutOrientation orientation, size_t defaultFocus)
: orientation(orientation)
, originalDefaultFocus(defaultFocus)
, defaultFocusedIndex(defaultFocus)
{
}
void BoxLayout::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
// Draw children
for (BoxLayoutChild* child : this->children)
child->view->frame(ctx);
}
void BoxLayout::setGravity(BoxLayoutGravity gravity)
{
this->gravity = gravity;
this->invalidate();
}
void BoxLayout::setSpacing(unsigned spacing)
{
this->spacing = spacing;
this->invalidate();
}
unsigned BoxLayout::getSpacing()
{
return this->spacing;
}
void BoxLayout::setMargins(unsigned top, unsigned right, unsigned bottom, unsigned left)
{
this->marginBottom = bottom;
this->marginLeft = left;
this->marginRight = right;
this->marginTop = top;
this->invalidate();
}
void BoxLayout::setMarginBottom(unsigned bottom)
{
this->marginBottom = bottom;
this->invalidate();
}
size_t BoxLayout::getViewsCount()
{
return this->children.size();
}
View* BoxLayout::getDefaultFocus()
{
// Focus default focus first
if (this->defaultFocusedIndex < this->children.size())
{
View* newFocus = this->children[this->defaultFocusedIndex]->view->getDefaultFocus();
if (newFocus)
return newFocus;
}
// Fallback to finding the first focusable view
for (size_t i = 0; i < this->children.size(); i++)
{
View* newFocus = this->children[i]->view->getDefaultFocus();
if (newFocus)
return newFocus;
}
return nullptr;
}
View* BoxLayout::getNextFocus(FocusDirection direction, void* parentUserData)
{
// Return nullptr immediately if focus direction mismatches the layout direction
if ((this->orientation == BoxLayoutOrientation::HORIZONTAL && direction != FocusDirection::LEFT && direction != FocusDirection::RIGHT) || (this->orientation == BoxLayoutOrientation::VERTICAL && direction != FocusDirection::UP && direction != FocusDirection::DOWN))
{
return nullptr;
}
// Traverse the children
size_t offset = 1; // which way are we going in the children list
if ((this->orientation == BoxLayoutOrientation::HORIZONTAL && direction == FocusDirection::LEFT) || (this->orientation == BoxLayoutOrientation::VERTICAL && direction == FocusDirection::UP))
{
offset = -1;
}
size_t currentFocusIndex = *((size_t*)parentUserData) + offset;
View* currentFocus = nullptr;
while (!currentFocus && currentFocusIndex >= 0 && currentFocusIndex < this->children.size())
{
currentFocus = this->children[currentFocusIndex]->view->getDefaultFocus();
currentFocusIndex += offset;
}
return currentFocus;
}
void BoxLayout::removeView(int index, bool free)
{
BoxLayoutChild* toRemove = this->children[index];
toRemove->view->willDisappear(true);
if (free)
delete toRemove->view;
delete toRemove;
this->children.erase(this->children.begin() + index);
}
void BoxLayout::clear(bool free)
{
while (!this->children.empty())
this->removeView(0, free);
}
void BoxLayout::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Vertical orientation
if (this->orientation == BoxLayoutOrientation::VERTICAL)
{
unsigned entriesHeight = 0;
int yAdvance = this->y + this->marginTop;
for (size_t i = 0; i < this->children.size(); i++)
{
BoxLayoutChild* child = this->children[i];
unsigned childHeight = child->view->getHeight();
if (child->fill)
child->view->setBoundaries(this->x + this->marginLeft,
yAdvance,
this->width - this->marginLeft - this->marginRight,
this->y + this->height - yAdvance - this->marginBottom);
else
child->view->setBoundaries(this->x + this->marginLeft,
yAdvance,
this->width - this->marginLeft - this->marginRight,
child->view->getHeight(false));
child->view->invalidate(true); // call layout directly in case height is updated
childHeight = child->view->getHeight();
int spacing = (int)this->spacing;
View* next = (this->children.size() > 1 && i <= this->children.size() - 2) ? this->children[i + 1]->view : nullptr;
this->customSpacing(child->view, next, &spacing);
if (child->view->isCollapsed())
spacing = 0;
if (!child->view->isHidden())
entriesHeight += spacing + childHeight;
yAdvance += spacing + childHeight;
}
// TODO: apply gravity
// Update height if needed
if (this->resize)
this->setHeight(entriesHeight - spacing + this->marginTop + this->marginBottom);
}
// Horizontal orientation
else if (this->orientation == BoxLayoutOrientation::HORIZONTAL)
{
// Layout
int xAdvance = this->x + this->marginLeft;
for (size_t i = 0; i < this->children.size(); i++)
{
BoxLayoutChild* child = this->children[i];
unsigned childWidth = child->view->getWidth();
if (child->fill)
child->view->setBoundaries(xAdvance,
this->y + this->marginTop,
this->x + this->width - xAdvance - this->marginRight,
this->height - this->marginTop - this->marginBottom);
else
child->view->setBoundaries(xAdvance,
this->y + this->marginTop,
childWidth,
this->height - this->marginTop - this->marginBottom);
child->view->invalidate(true); // call layout directly in case width is updated
childWidth = child->view->getWidth();
int spacing = (int)this->spacing;
View* next = (this->children.size() > 1 && i <= this->children.size() - 2) ? this->children[i + 1]->view : nullptr;
this->customSpacing(child->view, next, &spacing);
if (child->view->isCollapsed())
spacing = 0;
xAdvance += spacing + childWidth;
}
// Apply gravity
// TODO: more efficient gravity implementation?
if (!this->children.empty())
{
switch (this->gravity)
{
case BoxLayoutGravity::RIGHT:
{
// Take the remaining empty space between the last view's
// right boundary and ours and push all views by this amount
View* lastView = this->children[this->children.size() - 1]->view;
unsigned lastViewRight = lastView->getX() + lastView->getWidth();
unsigned ourRight = this->getX() + this->getWidth();
if (lastViewRight <= ourRight)
{
unsigned difference = ourRight - lastViewRight;
for (BoxLayoutChild* child : this->children)
{
View* view = child->view;
view->setBoundaries(
view->getX() + difference,
view->getY(),
view->getWidth(),
view->getHeight());
view->invalidate();
}
}
break;
}
default:
break;
}
}
// TODO: update width if needed (introduce entriesWidth)
}
}
void BoxLayout::setResize(bool resize)
{
this->resize = resize;
this->invalidate();
}
void BoxLayout::addView(View* view, bool fill, bool resetState)
{
BoxLayoutChild* child = new BoxLayoutChild();
child->view = view;
child->fill = fill;
this->children.push_back(child);
size_t position = this->children.size() - 1;
size_t* userdata = (size_t*)malloc(sizeof(size_t));
*userdata = position;
view->setParent(this, userdata);
view->willAppear(resetState);
this->invalidate();
}
View* BoxLayout::getChild(size_t index)
{
return this->children[index]->view;
}
bool BoxLayout::isEmpty()
{
return this->children.size() == 0;
}
bool BoxLayout::isChildFocused()
{
return this->childFocused;
}
void BoxLayout::onChildFocusGained(View* child)
{
this->childFocused = true;
// Remember focus if needed
if (this->rememberFocus)
{
size_t index = *((size_t*)child->getParentUserData());
this->defaultFocusedIndex = index;
}
View::onChildFocusGained(child);
}
void BoxLayout::onChildFocusLost(View* child)
{
this->childFocused = false;
View::onChildFocusLost(child);
}
BoxLayout::~BoxLayout()
{
for (BoxLayoutChild* child : this->children)
{
child->view->willDisappear(true);
delete child->view;
delete child;
}
this->children.clear();
}
void BoxLayout::willAppear(bool resetState)
{
for (BoxLayoutChild* child : this->children)
child->view->willAppear(resetState);
}
void BoxLayout::willDisappear(bool resetState)
{
for (BoxLayoutChild* child : this->children)
child->view->willDisappear(resetState);
// Reset default focus to original one if needed
if (this->rememberFocus)
this->defaultFocusedIndex = this->originalDefaultFocus;
}
void BoxLayout::onWindowSizeChanged()
{
for (BoxLayoutChild* child : this->children)
child->view->onWindowSizeChanged();
}
void BoxLayout::setRememberFocus(bool remember)
{
this->rememberFocus = remember;
}
} // namespace brls

View File

@@ -0,0 +1,213 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <borealis/application.hpp>
#include <borealis/button.hpp>
namespace brls
{
Button::Button(ButtonStyle style)
: style(style)
{
this->registerAction("OK", Key::A, [this] { return this->onClick(); });
}
LabelStyle Button::getLabelStyle()
{
if (this->style == ButtonStyle::BORDERLESS)
return LabelStyle::BUTTON_BORDERLESS;
else if (this->style == ButtonStyle::DIALOG)
return LabelStyle::BUTTON_DIALOG;
else if (this->style == ButtonStyle::CRASH)
return LabelStyle::CRASH;
if (this->state == ButtonState::DISABLED)
return LabelStyle::BUTTON_PLAIN_DISABLED;
else
return LabelStyle::BUTTON_PLAIN;
}
Button::~Button()
{
if (this->label != nullptr)
delete this->label;
if (this->image != nullptr)
delete this->image;
}
void Button::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
unsigned imageWidth = this->label ? this->getHeight() : this->getWidth();
unsigned imageHeight = this->getHeight();
if (!this->image)
imageWidth = 0;
if (this->label != nullptr)
{
this->label->setWidth(this->getWidth() - imageWidth);
this->label->invalidate(true);
this->label->setBoundaries(
this->x + imageWidth,
this->y + this->getHeight() / 2 - this->label->getHeight() / 2,
this->label->getWidth(),
this->label->getHeight());
this->label->invalidate();
}
if (this->image != nullptr)
{
this->image->setHeight(imageHeight);
this->image->setWidth(imageWidth);
this->image->invalidate(true);
this->image->setBoundaries(
this->x,
this->y + this->getHeight() / 2 - this->image->getHeight() / 2,
this->image->getWidth(),
this->image->getHeight());
}
}
Button* Button::setLabel(std::string label)
{
if (this->label != nullptr)
delete this->label;
this->label = new Label(this->getLabelStyle(), label, true);
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
this->label->setParent(this);
return this;
}
Button* Button::setImage(std::string path)
{
this->image = new Image(path);
this->image->setParent(this);
return this;
}
Button* Button::setImage(unsigned char* buffer, size_t bufferSize)
{
this->image = new Image(buffer, bufferSize);
this->image->setParent(this);
return this;
}
void Button::setState(ButtonState state)
{
this->state = state;
if (this->label != nullptr)
this->label->setStyle(this->getLabelStyle());
}
ButtonState Button::getState()
{
return this->state;
}
void Button::getHighlightInsets(unsigned* top, unsigned* right, unsigned* bottom, unsigned* left)
{
if (this->style == ButtonStyle::DIALOG)
{
View::getHighlightInsets(top, right, bottom, left);
*right -= 1;
return;
}
Style* style = Application::getStyle();
*top = style->Button.highlightInset;
*right = style->Button.highlightInset;
*bottom = style->Button.highlightInset;
*left = style->Button.highlightInset;
}
void Button::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
float cornerRadius = this->cornerRadiusOverride ? this->cornerRadiusOverride : (float)style->Button.cornerRadius;
// Background
switch (this->style)
{
case ButtonStyle::PLAIN:
{
nvgFillColor(vg, a(this->state == ButtonState::DISABLED ? ctx->theme->buttonPlainDisabledBackgroundColor : ctx->theme->buttonPlainEnabledBackgroundColor));
nvgBeginPath(vg);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgFill(vg);
break;
}
default:
break;
}
// Shadow
if (this->state == ButtonState::ENABLED && this->style == ButtonStyle::PLAIN)
{
float shadowWidth = style->Button.shadowWidth;
float shadowFeather = style->Button.shadowFeather;
float shadowOpacity = style->Button.shadowOpacity;
float shadowOffset = style->Button.shadowOffset;
NVGpaint shadowPaint = nvgBoxGradient(vg,
x, y + shadowWidth,
width, height,
cornerRadius * 2, shadowFeather,
RGBA(0, 0, 0, shadowOpacity * alpha), transparent);
nvgBeginPath(vg);
nvgRect(vg, x - shadowOffset, y - shadowOffset,
width + shadowOffset * 2, height + shadowOffset * 3);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgPathWinding(vg, NVG_HOLE);
nvgFillPaint(vg, shadowPaint);
nvgFill(vg);
}
// Label
if (this->label != nullptr)
this->label->frame(ctx);
if (this->image != nullptr)
this->image->frame(ctx);
}
bool Button::onClick()
{
if (this->state == ButtonState::DISABLED)
return false;
return this->clickEvent.fire(this);
}
GenericEvent* Button::getClickEvent()
{
return &this->clickEvent;
}
void Button::setCornerRadius(float cornerRadius)
{
this->cornerRadiusOverride = cornerRadius;
if (this->image != nullptr)
this->image->setCornerRadius(cornerRadius);
}
} // namespace brls

View File

@@ -0,0 +1,151 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/crash_frame.hpp>
namespace brls
{
CrashFrame::CrashFrame(std::string text)
{
// Label
this->label = new Label(LabelStyle::CRASH, text, true);
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
this->label->setParent(this);
// Button
this->button = (new Button(ButtonStyle::CRASH))->setLabel("OK");
this->button->setParent(this);
this->button->alpha = 0.0f;
this->button->getClickEvent()->subscribe([](View* view) { Application::quit(); });
this->button->overrideThemeVariant(Application::getThemeValuesForVariant(ThemeVariant_DARK));
// Hint
this->hint = new Hint();
this->hint->setParent(this);
}
void CrashFrame::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
nvgSave(vg);
// Background
nvgFillColor(vg, RGB(0, 0, 0));
nvgBeginPath(vg);
nvgRect(vg, x, y, width, height);
nvgFill(vg);
// Scale
float scale = (this->alpha + 2.0f) / 3.0f;
nvgTranslate(vg, (1.0f - scale) * width * 0.5f, (1.0f - scale) * height * 0.5f);
nvgScale(vg, scale, scale);
// Label
this->label->frame(ctx);
// [!] box
unsigned boxSize = style->CrashFrame.boxSize;
nvgStrokeColor(vg, RGB(255, 255, 255));
nvgStrokeWidth(vg, style->CrashFrame.boxStrokeWidth);
nvgBeginPath(vg);
nvgRect(vg, x + width / 2 - boxSize / 2, y + style->CrashFrame.boxSpacing, boxSize, boxSize);
nvgStroke(vg);
nvgFillColor(vg, RGB(255, 255, 255));
nvgFontSize(vg, (float)style->CrashFrame.boxSize / 1.25f);
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + width / 2, y + style->CrashFrame.boxSpacing + boxSize / 2, "!", nullptr);
nvgFill(vg);
// End scale
nvgResetTransform(vg);
nvgRestore(vg);
// Footer
nvgBeginPath(vg);
nvgRect(vg, x + style->AppletFrame.separatorSpacing, y + height - style->AppletFrame.footerHeight, width - style->AppletFrame.separatorSpacing * 2, 1);
nvgFill(vg);
nvgFontSize(vg, style->AppletFrame.footerTextSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + style->AppletFrame.separatorSpacing + style->AppletFrame.footerTextSpacing, y + height - style->AppletFrame.footerHeight / 2, Application::getTitle().c_str(), nullptr);
// Button
this->button->frame(ctx);
// Hint
this->hint->frame(ctx);
}
void CrashFrame::onShowAnimationEnd()
{
this->button->show([]() {});
}
View* CrashFrame::getDefaultFocus()
{
return this->button->getDefaultFocus();
}
void CrashFrame::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Label
this->label->setWidth(roundf((float)this->width * style->CrashFrame.labelWidth));
this->label->invalidate(true);
this->label->setBoundaries(
this->x + this->width / 2 - this->label->getWidth() / 2,
this->y + (this->height - style->AppletFrame.footerHeight) / 2,
this->label->getWidth(),
this->label->getHeight());
// Button
this->button->setBoundaries(
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
this->y + this->height - style->AppletFrame.footerHeight - style->CrashFrame.boxSpacing - style->CrashFrame.buttonHeight,
style->CrashFrame.buttonWidth,
style->CrashFrame.buttonHeight);
this->button->invalidate();
// Hint
// TODO: convert the bottom-left footer into a Label to get its width and avoid clipping with the hint
unsigned hintWidth = this->width - style->AppletFrame.separatorSpacing * 2 - style->AppletFrame.footerTextSpacing * 2;
this->hint->setBoundaries(
this->x + this->width - hintWidth - style->AppletFrame.separatorSpacing - style->AppletFrame.footerTextSpacing,
this->y + this->height - style->AppletFrame.footerHeight,
hintWidth,
style->AppletFrame.footerHeight);
this->hint->invalidate();
}
CrashFrame::~CrashFrame()
{
delete this->label;
delete this->hint;
}
} // namespace brls

View File

@@ -0,0 +1,342 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/button.hpp>
#include <borealis/dialog.hpp>
// TODO: different open animation?
namespace brls
{
Dialog::Dialog(View* contentView)
: contentView(contentView)
{
if (contentView)
contentView->setParent(this);
this->registerAction("Back", Key::B, [this] { return this->onCancel(); });
}
Dialog::Dialog(std::string text)
: Dialog(new Label(LabelStyle::DIALOG, text, true))
{
}
void Dialog::addButton(std::string label, GenericEvent::Callback cb)
{
if (this->buttons.size() >= 3)
return;
DialogButton* button = new DialogButton();
button->label = label;
button->cb = cb;
this->buttons.push_back(button);
this->rebuildButtons();
this->invalidate();
}
void Dialog::open()
{
Application::pushView(this);
if (this->buttons.size() == 0)
Application::blockInputs();
}
// TODO: do something better in case another view was pushed in the meantime
void Dialog::close(std::function<void(void)> cb)
{
Application::popView(ViewAnimation::FADE, cb);
if (this->buttons.size() == 0)
Application::unblockInputs();
}
void Dialog::setCancelable(bool cancelable)
{
this->cancelable = cancelable;
}
void Dialog::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
// Backdrop
nvgFillColor(vg, a(ctx->theme->dialogBackdrop));
nvgBeginPath(vg);
nvgRect(vg, x, y, width, height);
nvgFill(vg);
// Shadow
float shadowWidth = style->Dialog.shadowWidth;
float shadowFeather = style->Dialog.shadowFeather;
float shadowOpacity = style->Dialog.shadowOpacity;
float shadowOffset = style->Dialog.shadowOffset;
NVGpaint shadowPaint = nvgBoxGradient(vg,
this->frameX, this->frameY + shadowWidth,
this->frameWidth, this->frameHeight,
style->Dialog.cornerRadius * 2, shadowFeather,
RGBA(0, 0, 0, shadowOpacity * alpha), transparent);
nvgBeginPath(vg);
nvgRect(vg, this->frameX - shadowOffset, this->frameY - shadowOffset,
this->frameWidth + shadowOffset * 2, this->frameHeight + shadowOffset * 3);
nvgRoundedRect(vg, this->frameX, this->frameY, this->frameWidth, this->frameHeight, style->Dialog.cornerRadius);
nvgPathWinding(vg, NVG_HOLE);
nvgFillPaint(vg, shadowPaint);
nvgFill(vg);
// Frame
nvgFillColor(vg, a(ctx->theme->dialogColor));
nvgBeginPath(vg);
nvgRoundedRect(vg, this->frameX, this->frameY, this->frameWidth, this->frameHeight, style->Dialog.cornerRadius);
nvgFill(vg);
// Content view
if (this->contentView)
this->contentView->frame(ctx);
// Buttons separator
if (this->buttons.size() > 0)
{
unsigned buttonsHeight = this->getButtonsHeight();
nvgFillColor(vg, a(ctx->theme->dialogButtonSeparatorColor));
// First vertical separator
nvgBeginPath(vg);
nvgRect(vg, this->frameX, this->frameY + this->frameHeight - buttonsHeight, this->frameWidth, style->Dialog.buttonSeparatorHeight);
nvgFill(vg);
// Second vertical separator
if (this->buttons.size() == 3)
{
nvgBeginPath(vg);
nvgRect(vg, this->frameX, this->frameY + this->frameHeight - style->Dialog.buttonHeight, this->frameWidth, style->Dialog.buttonSeparatorHeight);
nvgFill(vg);
}
// Horizontal separator
if (this->buttons.size() >= 2)
{
nvgBeginPath(vg);
nvgRect(
vg,
this->frameX + this->frameWidth / 2 + style->Dialog.buttonSeparatorHeight / 2,
this->frameY + this->frameHeight - style->Dialog.buttonHeight + 1, // offset by 1 to fix aliasing artifact
style->Dialog.buttonSeparatorHeight,
style->Dialog.buttonHeight - 1);
nvgFill(vg);
}
}
// Buttons
if (this->verticalButtonsLayout)
this->verticalButtonsLayout->frame(ctx);
}
View* Dialog::getDefaultFocus()
{
if (this->buttons.size() > 0 && this->verticalButtonsLayout)
return this->verticalButtonsLayout->getDefaultFocus();
return nullptr;
}
bool Dialog::onCancel()
{
if (this->cancelable)
this->close();
return this->cancelable;
}
unsigned Dialog::getButtonsHeight()
{
Style* style = Application::getStyle();
if (this->buttons.size() == 3)
return style->Dialog.buttonHeight * 2;
else if (this->buttons.size() > 0) // 1 or 2
return style->Dialog.buttonHeight;
else
return 0;
}
void Dialog::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
this->frameWidth = style->Dialog.width;
this->frameHeight = style->Dialog.height;
unsigned buttonsHeight = this->getButtonsHeight();
this->frameHeight += buttonsHeight;
this->frameX = getWidth() / 2 - this->frameWidth / 2;
this->frameY = getHeight() / 2 - this->frameHeight / 2;
unsigned contentX = this->frameX + style->Dialog.paddingLeftRight;
unsigned contentY = this->frameY + style->Dialog.paddingTopBottom;
unsigned contentWidth = this->frameWidth - style->Dialog.paddingLeftRight * 2;
unsigned contentHeight = this->frameHeight - style->Dialog.paddingTopBottom * 2 - buttonsHeight;
if (this->contentView)
{
// First layout to get height
this->contentView->setBoundaries(
contentX,
contentY,
contentWidth,
contentHeight);
this->contentView->invalidate(true); // layout directly to get height
// Center the content view in the dialog
// or resize it if needed
unsigned newContentHeight = this->contentView->getHeight();
int difference = contentHeight - newContentHeight;
if (difference < 0)
{
this->frameHeight += -difference;
}
else
{
contentY += difference / 2;
this->contentView->setBoundaries(
contentX,
contentY,
contentWidth,
contentHeight);
this->contentView->invalidate();
}
}
// Buttons
if (this->verticalButtonsLayout)
{
this->verticalButtonsLayout->setBoundaries(
this->frameX,
this->frameY + this->frameHeight - buttonsHeight,
this->frameWidth,
style->Dialog.buttonHeight);
// Only one big button
if (this->buttons.size() == 1)
{
this->verticalButtonsLayout->getChild(0)->setHeight(style->Dialog.buttonHeight);
}
// Two buttons on one row
else if (this->buttons.size() == 2)
{
this->horizontalButtonsLayout->setHeight(style->Dialog.buttonHeight);
this->horizontalButtonsLayout->getChild(0)->setWidth(this->frameWidth / 2);
this->horizontalButtonsLayout->getChild(1)->setWidth(this->frameWidth / 2);
}
// Two rows: one with one button and one with two
else if (this->buttons.size() == 3)
{
this->verticalButtonsLayout->getChild(0)->setHeight(style->Dialog.buttonHeight);
this->horizontalButtonsLayout->setHeight(style->Dialog.buttonHeight);
this->horizontalButtonsLayout->getChild(0)->setWidth(this->frameWidth / 2);
this->horizontalButtonsLayout->getChild(1)->setWidth(this->frameWidth / 2);
}
this->verticalButtonsLayout->invalidate();
if (this->horizontalButtonsLayout)
this->horizontalButtonsLayout->invalidate();
}
}
void Dialog::rebuildButtons()
{
if (this->verticalButtonsLayout)
delete this->verticalButtonsLayout;
this->verticalButtonsLayout = nullptr;
// horizontal box layout will be deleted by
// the vertical layout destructor
if (this->buttons.size() > 0)
{
this->verticalButtonsLayout = new BoxLayout(BoxLayoutOrientation::VERTICAL);
this->verticalButtonsLayout->setParent(this);
// Only one big button
if (this->buttons.size() == 1)
{
Button* button = (new Button(ButtonStyle::DIALOG))->setLabel(this->buttons[0]->label);
button->getClickEvent()->subscribe(this->buttons[0]->cb);
this->verticalButtonsLayout->addView(button);
}
// Two buttons on one row
else if (this->buttons.size() == 2)
{
this->horizontalButtonsLayout = new BoxLayout(BoxLayoutOrientation::HORIZONTAL);
this->verticalButtonsLayout->addView(this->horizontalButtonsLayout);
for (DialogButton* dialogButton : this->buttons)
{
Button* button = (new Button(ButtonStyle::DIALOG))->setLabel(dialogButton->label);
button->getClickEvent()->subscribe(dialogButton->cb);
this->horizontalButtonsLayout->addView(button);
}
}
// Two rows: one with one button and one with two
else if (this->buttons.size() == 3)
{
Button* button = (new Button(ButtonStyle::DIALOG))->setLabel(this->buttons[0]->label);
button->getClickEvent()->subscribe(this->buttons[0]->cb);
this->verticalButtonsLayout->addView(button);
this->horizontalButtonsLayout = new BoxLayout(BoxLayoutOrientation::HORIZONTAL);
this->verticalButtonsLayout->addView(this->horizontalButtonsLayout);
for (size_t i = 1; i < this->buttons.size(); i++)
{
DialogButton* dialogButton = this->buttons[i];
Button* button = (new Button(ButtonStyle::DIALOG))->setLabel(dialogButton->label);
button->getClickEvent()->subscribe(dialogButton->cb);
this->horizontalButtonsLayout->addView(button);
}
}
}
}
Dialog::~Dialog()
{
if (this->contentView)
delete this->contentView;
if (this->verticalButtonsLayout)
delete this->verticalButtonsLayout;
for (DialogButton* dialogButton : this->buttons)
delete dialogButton;
// horizontal box layout will be deleted by
// the vertical layout destructor
}
} // namespace brls

View File

@@ -0,0 +1,214 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/dropdown.hpp>
#include <borealis/logger.hpp>
#define SELECT_VIEW_MAX_ITEMS 6 // for max height
#define min(a, b) ((a < b) ? a : b)
// TODO: Turns out the fade out animation is the same as the fade in (top -> bottom)
namespace brls
{
Dropdown::Dropdown(std::string title, std::vector<std::string> values, ValueSelectedEvent::Callback cb, size_t selected)
: title(title)
{
Style* style = Application::getStyle();
this->valueEvent.subscribe(cb);
this->topOffset = (float)style->Dropdown.listPadding / 8.0f;
this->valuesCount = values.size();
this->list = new List(selected);
this->list->setParent(this);
this->list->setMargins(1, 0, 1, 0);
for (size_t i = 0; i < values.size(); i++)
{
std::string value = values[i];
ListItem* item = new ListItem(value);
if (i == selected)
item->setChecked(true);
item->setHeight(style->Dropdown.listItemHeight);
item->setTextSize(style->Dropdown.listItemTextSize);
item->getClickEvent()->subscribe([this, i](View* view) {
this->valueEvent.fire(i);
Application::popView();
});
this->list->addView(item);
}
this->hint = new Hint();
this->hint->setParent(this);
this->registerAction("Back", Key::B, [this] { return this->onCancel(); });
}
void Dropdown::show(std::function<void(void)> cb, bool animate, ViewAnimation animation)
{
View::show(cb);
menu_animation_ctx_entry_t entry;
entry.duration = this->getShowAnimationDuration(animation);
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->topOffset;
entry.tag = (uintptr_t) nullptr;
entry.target_value = 0.0f;
entry.tick = [this](void* userdata) { this->invalidate(); };
entry.userdata = nullptr;
menu_animation_push(&entry);
}
void Dropdown::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
unsigned top = this->list->getY() - style->Dropdown.headerHeight - style->Dropdown.listPadding;
// Backdrop
nvgFillColor(vg, a(ctx->theme->dropdownBackgroundColor));
nvgBeginPath(vg);
nvgRect(vg, x, y, width, top);
nvgFill(vg);
// TODO: Shadow
// Background
nvgFillColor(vg, a(ctx->theme->sidebarColor));
nvgBeginPath(vg);
nvgRect(vg, x, top, width, height - top);
nvgFill(vg);
// List
this->list->frame(ctx);
// Footer
this->hint->frame(ctx);
nvgFillColor(vg, ctx->theme->separatorColor); // we purposely don't apply opacity
nvgBeginPath(vg);
nvgRect(vg, x + style->AppletFrame.separatorSpacing, y + height - style->AppletFrame.footerHeight, width - style->AppletFrame.separatorSpacing * 2, 1);
nvgFill(vg);
nvgFillColor(vg, ctx->theme->textColor); // we purposely don't apply opacity
nvgFontSize(vg, style->AppletFrame.footerTextSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + style->AppletFrame.separatorSpacing + style->AppletFrame.footerTextSpacing, y + height - style->AppletFrame.footerHeight / 2, Application::getCommonFooter()->c_str(), nullptr);
// Header
nvgFillColor(vg, a(ctx->theme->separatorColor));
nvgBeginPath(vg);
nvgRect(vg, x + style->AppletFrame.separatorSpacing, top + style->Dropdown.headerHeight - 1, width - style->AppletFrame.separatorSpacing * 2, 1);
nvgFill(vg);
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->textColor));
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, style->Dropdown.headerFontSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgText(vg, x + style->Dropdown.headerPadding, top + style->Dropdown.headerHeight / 2, this->title.c_str(), nullptr);
}
bool Dropdown::onCancel()
{
this->valueEvent.fire(-1);
Application::popView();
return true;
}
unsigned Dropdown::getShowAnimationDuration(ViewAnimation animation)
{
return View::getShowAnimationDuration(animation) / 2;
}
void Dropdown::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Layout and move the list
unsigned listHeight = min(SELECT_VIEW_MAX_ITEMS, this->valuesCount) * style->Dropdown.listItemHeight - (unsigned)this->topOffset;
unsigned listWidth = style->Dropdown.listWidth + style->List.marginLeftRight * 2;
this->list->setBoundaries(
this->width / 2 - listWidth / 2,
this->height - style->AppletFrame.footerHeight - listHeight - style->Dropdown.listPadding + (unsigned)this->topOffset,
listWidth,
listHeight);
this->list->invalidate(true); // call layout directly to update scrolling
// Hint
// TODO: convert the bottom-left footer into a Label to get its width and avoid clipping with the hint
unsigned hintWidth = this->width - style->AppletFrame.separatorSpacing * 2 - style->AppletFrame.footerTextSpacing * 2;
this->hint->setBoundaries(
this->x + this->width - hintWidth - style->AppletFrame.separatorSpacing - style->AppletFrame.footerTextSpacing,
this->y + this->height - style->AppletFrame.footerHeight,
hintWidth,
style->AppletFrame.footerHeight);
this->hint->invalidate();
}
View* Dropdown::getDefaultFocus()
{
return this->list->getDefaultFocus();
}
void Dropdown::open(std::string title, std::vector<std::string> values, ValueSelectedEvent::Callback cb, int selected)
{
Dropdown* dropdown = new Dropdown(title, values, cb, selected);
Application::pushView(dropdown);
}
void Dropdown::willAppear(bool resetState)
{
if (this->list)
this->list->willAppear(resetState);
if (this->hint)
this->hint->willAppear(resetState);
}
void Dropdown::willDisappear(bool resetState)
{
if (this->list)
this->list->willDisappear(resetState);
if (this->hint)
this->hint->willDisappear(resetState);
}
Dropdown::~Dropdown()
{
delete this->list;
delete this->hint;
}
} // namespace brls

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
*.dll
*.exe
*.c~
*.un~
*.o

View File

@@ -0,0 +1,69 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (compat_strl.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <ctype.h>
#include <compat/strl.h>
/* Implementation of strlcpy()/strlcat() based on OpenBSD. */
#ifndef __MACH__
size_t strlcpy(char *dest, const char *source, size_t size)
{
size_t src_size = 0;
size_t n = size;
if (n)
while (--n && (*dest++ = *source++)) src_size++;
if (!n)
{
if (size) *dest = '\0';
while (*source++) src_size++;
}
return src_size;
}
size_t strlcat(char *dest, const char *source, size_t size)
{
size_t len = strlen(dest);
dest += len;
if (len > size)
size = 0;
else
size -= len;
return len + strlcpy(dest, source, size);
}
#endif
char *strldup(const char *s, size_t n)
{
char *dst = (char*)malloc(sizeof(char) * (n + 1));
strlcpy(dst, s, n);
return dst;
}

View File

@@ -0,0 +1,510 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (encoding_utf.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <boolean.h>
#include <compat/strl.h>
#include <retro_inline.h>
#include <encodings/utf.h>
#if defined(_WIN32) && !defined(_XBOX)
#include <windows.h>
#elif defined(_XBOX)
#include <xtl.h>
#endif
static unsigned leading_ones(uint8_t c)
{
unsigned ones = 0;
while (c & 0x80)
{
ones++;
c <<= 1;
}
return ones;
}
/* Simple implementation. Assumes the sequence is
* properly synchronized and terminated. */
size_t utf8_conv_utf32(uint32_t *out, size_t out_chars,
const char *in, size_t in_size)
{
unsigned i;
size_t ret = 0;
while (in_size && out_chars)
{
unsigned extra, shift;
uint32_t c;
uint8_t first = *in++;
unsigned ones = leading_ones(first);
if (ones > 6 || ones == 1) /* Invalid or desync. */
break;
extra = ones ? ones - 1 : ones;
if (1 + extra > in_size) /* Overflow. */
break;
shift = (extra - 1) * 6;
c = (first & ((1 << (7 - ones)) - 1)) << (6 * extra);
for (i = 0; i < extra; i++, in++, shift -= 6)
c |= (*in & 0x3f) << shift;
*out++ = c;
in_size -= 1 + extra;
out_chars--;
ret++;
}
return ret;
}
bool utf16_conv_utf8(uint8_t *out, size_t *out_chars,
const uint16_t *in, size_t in_size)
{
static uint8_t kUtf8Limits[5] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
size_t out_pos = 0;
size_t in_pos = 0;
for (;;)
{
unsigned numAdds;
uint32_t value;
if (in_pos == in_size)
{
*out_chars = out_pos;
return true;
}
value = in[in_pos++];
if (value < 0x80)
{
if (out)
out[out_pos] = (char)value;
out_pos++;
continue;
}
if (value >= 0xD800 && value < 0xE000)
{
uint32_t c2;
if (value >= 0xDC00 || in_pos == in_size)
break;
c2 = in[in_pos++];
if (c2 < 0xDC00 || c2 >= 0xE000)
break;
value = (((value - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000;
}
for (numAdds = 1; numAdds < 5; numAdds++)
if (value < (((uint32_t)1) << (numAdds * 5 + 6)))
break;
if (out)
out[out_pos] = (char)(kUtf8Limits[numAdds - 1]
+ (value >> (6 * numAdds)));
out_pos++;
do
{
numAdds--;
if (out)
out[out_pos] = (char)(0x80
+ ((value >> (6 * numAdds)) & 0x3F));
out_pos++;
}while (numAdds != 0);
}
*out_chars = out_pos;
return false;
}
/* Acts mostly like strlcpy.
*
* Copies the given number of UTF-8 characters,
* but at most d_len bytes.
*
* Always NULL terminates.
* Does not copy half a character.
*
* Returns number of bytes. 's' is assumed valid UTF-8.
* Use only if 'chars' is considerably less than 'd_len'. */
size_t utf8cpy(char *d, size_t d_len, const char *s, size_t chars)
{
const uint8_t *sb = (const uint8_t*)s;
const uint8_t *sb_org = sb;
if (!s)
return 0;
while (*sb && chars-- > 0)
{
sb++;
while ((*sb & 0xC0) == 0x80) sb++;
}
if ((size_t)(sb - sb_org) > d_len-1 /* NUL */)
{
sb = sb_org + d_len-1;
while ((*sb & 0xC0) == 0x80) sb--;
}
memcpy(d, sb_org, sb-sb_org);
d[sb-sb_org] = '\0';
return sb-sb_org;
}
const char *utf8skip(const char *str, size_t chars)
{
const uint8_t *strb = (const uint8_t*)str;
if (!chars)
return str;
do
{
strb++;
while ((*strb & 0xC0)==0x80) strb++;
chars--;
} while(chars);
return (const char*)strb;
}
size_t utf8len(const char *string)
{
size_t ret = 0;
if (!string)
return 0;
while (*string)
{
if ((*string & 0xC0) != 0x80)
ret++;
string++;
}
return ret;
}
#define utf8_walkbyte(string) (*((*(string))++))
/* Does not validate the input, returns garbage if it's not UTF-8. */
uint32_t utf8_walk(const char **string)
{
uint8_t first = utf8_walkbyte(string);
uint32_t ret = 0;
if (first < 128)
return first;
ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F);
if (first >= 0xE0)
{
ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F);
if (first >= 0xF0)
{
ret = (ret << 6) | (utf8_walkbyte(string) & 0x3F);
return ret | (first & 7) << 18;
}
return ret | (first & 15) << 12;
}
return ret | (first & 31) << 6;
}
static bool utf16_to_char(uint8_t **utf_data,
size_t *dest_len, const uint16_t *in)
{
unsigned len = 0;
while (in[len] != '\0')
len++;
utf16_conv_utf8(NULL, dest_len, in, len);
*dest_len += 1;
*utf_data = (uint8_t*)malloc(*dest_len);
if (*utf_data == 0)
return false;
return utf16_conv_utf8(*utf_data, dest_len, in, len);
}
bool utf16_to_char_string(const uint16_t *in, char *s, size_t len)
{
size_t dest_len = 0;
uint8_t *utf16_data = NULL;
bool ret = utf16_to_char(&utf16_data, &dest_len, in);
if (ret)
{
utf16_data[dest_len] = 0;
strlcpy(s, (const char*)utf16_data, len);
}
free(utf16_data);
utf16_data = NULL;
return ret;
}
#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE)
/* Returned pointer MUST be freed by the caller if non-NULL. */
static char *mb_to_mb_string_alloc(const char *str,
enum CodePage cp_in, enum CodePage cp_out)
{
char *path_buf = NULL;
wchar_t *path_buf_wide = NULL;
int path_buf_len = 0;
int path_buf_wide_len = MultiByteToWideChar(cp_in, 0, str, -1, NULL, 0);
/* Windows 95 will return 0 from these functions with
* a UTF8 codepage set without MSLU.
*
* From an unknown MSDN version (others omit this info):
* - CP_UTF8 Windows 98/Me, Windows NT 4.0 and later:
* Translate using UTF-8. When this is set, dwFlags must be zero.
* - Windows 95: Under the Microsoft Layer for Unicode,
* MultiByteToWideChar also supports CP_UTF7 and CP_UTF8.
*/
if (path_buf_wide_len)
{
path_buf_wide = (wchar_t*)
calloc(path_buf_wide_len + sizeof(wchar_t), sizeof(wchar_t));
if (path_buf_wide)
{
MultiByteToWideChar(cp_in, 0,
str, -1, path_buf_wide, path_buf_wide_len);
if (*path_buf_wide)
{
path_buf_len = WideCharToMultiByte(cp_out, 0,
path_buf_wide, -1, NULL, 0, NULL, NULL);
if (path_buf_len)
{
path_buf = (char*)
calloc(path_buf_len + sizeof(char), sizeof(char));
if (path_buf)
{
WideCharToMultiByte(cp_out, 0,
path_buf_wide, -1, path_buf,
path_buf_len, NULL, NULL);
free(path_buf_wide);
if (*path_buf)
return path_buf;
free(path_buf);
return NULL;
}
}
else
{
free(path_buf_wide);
return strdup(str);
}
}
}
}
else
return strdup(str);
if (path_buf_wide)
free(path_buf_wide);
return NULL;
}
#endif
/* Returned pointer MUST be freed by the caller if non-NULL. */
char* utf8_to_local_string_alloc(const char *str)
{
if (str && *str)
{
#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE)
return mb_to_mb_string_alloc(str, CODEPAGE_UTF8, CODEPAGE_LOCAL);
#else
/* assume string needs no modification if not on Windows */
return strdup(str);
#endif
}
return NULL;
}
/* Returned pointer MUST be freed by the caller if non-NULL. */
char* local_to_utf8_string_alloc(const char *str)
{
if (str && *str)
{
#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE)
return mb_to_mb_string_alloc(str, CODEPAGE_LOCAL, CODEPAGE_UTF8);
#else
/* assume string needs no modification if not on Windows */
return strdup(str);
#endif
}
return NULL;
}
/* Returned pointer MUST be freed by the caller if non-NULL. */
wchar_t* utf8_to_utf16_string_alloc(const char *str)
{
#ifdef _WIN32
int len = 0;
int out_len = 0;
#else
size_t len = 0;
size_t out_len = 0;
#endif
wchar_t *buf = NULL;
if (!str || !*str)
return NULL;
#ifdef _WIN32
len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
if (len)
{
buf = (wchar_t*)calloc(len, sizeof(wchar_t));
if (!buf)
return NULL;
out_len = MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, len);
}
else
{
/* fallback to ANSI codepage instead */
len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
if (len)
{
buf = (wchar_t*)calloc(len, sizeof(wchar_t));
if (!buf)
return NULL;
out_len = MultiByteToWideChar(CP_ACP, 0, str, -1, buf, len);
}
}
if (out_len < 0)
{
free(buf);
return NULL;
}
#else
/* NOTE: For now, assume non-Windows platforms' locale is already UTF-8. */
len = mbstowcs(NULL, str, 0) + 1;
if (len)
{
buf = (wchar_t*)calloc(len, sizeof(wchar_t));
if (!buf)
return NULL;
out_len = mbstowcs(buf, str, len);
}
if (out_len == (size_t)-1)
{
free(buf);
return NULL;
}
#endif
return buf;
}
/* Returned pointer MUST be freed by the caller if non-NULL. */
char* utf16_to_utf8_string_alloc(const wchar_t *str)
{
#ifdef _WIN32
int len = 0;
#else
size_t len = 0;
#endif
char *buf = NULL;
if (!str || !*str)
return NULL;
#ifdef _WIN32
{
UINT code_page = CP_UTF8;
len = WideCharToMultiByte(code_page,
0, str, -1, NULL, 0, NULL, NULL);
/* fallback to ANSI codepage instead */
if (!len)
{
code_page = CP_ACP;
len = WideCharToMultiByte(code_page,
0, str, -1, NULL, 0, NULL, NULL);
}
buf = (char*)calloc(len, sizeof(char));
if (!buf)
return NULL;
if (WideCharToMultiByte(code_page,
0, str, -1, buf, len, NULL, NULL) < 0)
{
free(buf);
return NULL;
}
}
#else
/* NOTE: For now, assume non-Windows platforms'
* locale is already UTF-8. */
len = wcstombs(NULL, str, 0) + 1;
if (len)
{
buf = (char*)calloc(len, sizeof(char));
if (!buf)
return NULL;
if (wcstombs(buf, str, len) == (size_t)-1)
{
free(buf);
return NULL;
}
}
#endif
return buf;
}

View File

@@ -0,0 +1,889 @@
/* Copyright (C) 2010-2018 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (features_cpu.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <compat/strl.h>
#include <streams/file_stream.h>
#include <libretro.h>
#include <features/features_cpu.h>
#include <retro_timers.h>
#if defined(_WIN32) && !defined(_XBOX)
#include <windows.h>
#endif
#if defined(__CELLOS_LV2__)
#ifndef _PPU_INTRINSICS_H
#include <ppu_intrinsics.h>
#endif
#elif defined(_XBOX360)
#include <PPCIntrinsics.h>
#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(ANDROID) || defined(__QNX__) || defined(DJGPP)
/* POSIX_MONOTONIC_CLOCK is not being defined in Android headers despite support being present. */
#include <time.h>
#endif
#if defined(__QNX__) && !defined(CLOCK_MONOTONIC)
#define CLOCK_MONOTONIC 2
#endif
#if defined(PSP)
#include <pspkernel.h>
#include <sys/time.h>
#include <psprtc.h>
#endif
#if defined(VITA)
#include <psp2/kernel/processmgr.h>
#include <psp2/rtc.h>
#endif
#if defined(PS2)
#include <kernel.h>
#include <timer.h>
#include <time.h>
#endif
#if defined(__PSL1GHT__)
#include <sys/time.h>
#elif defined(__CELLOS_LV2__)
#include <sys/sys_time.h>
#endif
#ifdef GEKKO
#include <ogc/lwp_watchdog.h>
#endif
#ifdef WIIU
#include <wiiu/os/time.h>
#endif
#if defined(HAVE_LIBNX)
#include <switch.h>
#elif defined(SWITCH)
#include <libtransistor/types.h>
#include <libtransistor/svc.h>
#endif
#if defined(_3DS)
#include <3ds/svc.h>
#include <3ds/os.h>
#include <3ds/services/cfgu.h>
#endif
/* iOS/OSX specific. Lacks clock_gettime(), so implement it. */
#ifdef __MACH__
#include <sys/time.h>
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 0
#endif
#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#endif
/* this function is part of iOS 10 now */
static int ra_clock_gettime(int clk_ik, struct timespec *t)
{
struct timeval now;
int rv = gettimeofday(&now, NULL);
if (rv)
return rv;
t->tv_sec = now.tv_sec;
t->tv_nsec = now.tv_usec * 1000;
return 0;
}
#endif
#if defined(__MACH__) && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
#else
#define ra_clock_gettime clock_gettime
#endif
#ifdef EMSCRIPTEN
#include <emscripten.h>
#endif
#if defined(BSD) || defined(__APPLE__)
#include <sys/sysctl.h>
#endif
#include <string.h>
/**
* cpu_features_get_perf_counter:
*
* Gets performance counter.
*
* Returns: performance counter.
**/
retro_perf_tick_t cpu_features_get_perf_counter(void)
{
retro_perf_tick_t time_ticks = 0;
#if defined(_WIN32)
long tv_sec, tv_usec;
#if defined(_MSC_VER) && _MSC_VER <= 1200
static const unsigned __int64 epoch = 11644473600000000;
#else
static const unsigned __int64 epoch = 11644473600000000ULL;
#endif
FILETIME file_time;
SYSTEMTIME system_time;
ULARGE_INTEGER ularge;
GetSystemTime(&system_time);
SystemTimeToFileTime(&system_time, &file_time);
ularge.LowPart = file_time.dwLowDateTime;
ularge.HighPart = file_time.dwHighDateTime;
tv_sec = (long)((ularge.QuadPart - epoch) / 10000000L);
tv_usec = (long)(system_time.wMilliseconds * 1000);
time_ticks = (1000000 * tv_sec + tv_usec);
#elif defined(__linux__) || defined(__QNX__) || defined(__MACH__)
struct timespec tv = {0};
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0)
time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 +
(retro_perf_tick_t)tv.tv_nsec;
#elif defined(__GNUC__) && defined(__i386__) || defined(__i486__) || defined(__i686__) || defined(_M_X64) || defined(_M_AMD64)
__asm__ volatile ("rdtsc" : "=A" (time_ticks));
#elif defined(__GNUC__) && defined(__x86_64__) || defined(_M_IX86)
unsigned a, d;
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32);
#elif defined(__ARM_ARCH_6__)
__asm__ volatile( "mrc p15, 0, %0, c9, c13, 0" : "=r"(time_ticks) );
#elif defined(__CELLOS_LV2__) || defined(_XBOX360) || defined(__powerpc__) || defined(__ppc__) || defined(__POWERPC__)
time_ticks = __mftb();
#elif defined(GEKKO)
time_ticks = gettime();
#elif defined(PSP)
sceRtcGetCurrentTick((uint64_t*)&time_ticks);
#elif defined(VITA)
sceRtcGetCurrentTick((SceRtcTick*)&time_ticks);
#elif defined(PS2)
time_ticks = clock()*294912; // 294,912MHZ / 1000 msecs
#elif defined(_3DS)
time_ticks = svcGetSystemTick();
#elif defined(WIIU)
time_ticks = OSGetSystemTime();
#elif defined(__mips__)
struct timeval tv;
gettimeofday(&tv,NULL);
time_ticks = (1000000 * tv.tv_sec + tv.tv_usec);
#elif defined(HAVE_LIBNX)
time_ticks = armGetSystemTick();
#endif
return time_ticks;
}
/**
* cpu_features_get_time_usec:
*
* Gets time in microseconds.
*
* Returns: time in microseconds.
**/
retro_time_t cpu_features_get_time_usec(void)
{
#if defined(_WIN32)
static LARGE_INTEGER freq;
LARGE_INTEGER count;
/* Frequency is guaranteed to not change. */
if (!freq.QuadPart && !QueryPerformanceFrequency(&freq))
return 0;
if (!QueryPerformanceCounter(&count))
return 0;
return count.QuadPart * 1000000 / freq.QuadPart;
#elif defined(__CELLOS_LV2__)
return sys_time_get_system_time();
#elif defined(GEKKO)
return ticks_to_microsecs(gettime());
#elif defined(WIIU)
return ticks_to_us(OSGetSystemTime());
#elif defined(SWITCH) || defined(HAVE_LIBNX)
return (svcGetSystemTick() * 10) / 192;
#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(__QNX__) || defined(ANDROID) || defined(__MACH__)
struct timespec tv = {0};
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) < 0)
return 0;
return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000;
#elif defined(EMSCRIPTEN)
return emscripten_get_now() * 1000;
#elif defined(PS2)
return clock()*1000;
#elif defined(__mips__) || defined(DJGPP)
struct timeval tv;
gettimeofday(&tv,NULL);
return (1000000 * tv.tv_sec + tv.tv_usec);
#elif defined(_3DS)
return osGetTime() * 1000;
#elif defined(VITA)
return sceKernelGetProcessTimeWide();
#else
#error "Your platform does not have a timer function implemented in cpu_features_get_time_usec(). Cannot continue."
#endif
}
#if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__) || (defined(_M_X64) && _MSC_VER > 1310) || (defined(_M_IX86) && _MSC_VER > 1310)
#define CPU_X86
#endif
#if defined(_MSC_VER) && !defined(_XBOX)
#if (_MSC_VER > 1310)
#include <intrin.h>
#endif
#endif
#if defined(CPU_X86) && !defined(__MACH__)
void x86_cpuid(int func, int flags[4])
{
/* On Android, we compile RetroArch with PIC, and we
* are not allowed to clobber the ebx register. */
#ifdef __x86_64__
#define REG_b "rbx"
#define REG_S "rsi"
#else
#define REG_b "ebx"
#define REG_S "esi"
#endif
#if defined(__GNUC__)
__asm__ volatile (
"mov %%" REG_b ", %%" REG_S "\n"
"cpuid\n"
"xchg %%" REG_b ", %%" REG_S "\n"
: "=a"(flags[0]), "=S"(flags[1]), "=c"(flags[2]), "=d"(flags[3])
: "a"(func));
#elif defined(_MSC_VER)
__cpuid(flags, func);
#else
printf("Unknown compiler. Cannot check CPUID with inline assembly.\n");
memset(flags, 0, 4 * sizeof(int));
#endif
}
/* Only runs on i686 and above. Needs to be conditionally run. */
static uint64_t xgetbv_x86(uint32_t idx)
{
#if defined(__GNUC__)
uint32_t eax, edx;
__asm__ volatile (
/* Older GCC versions (Apple's GCC for example) do
* not understand xgetbv instruction.
* Stamp out the machine code directly.
*/
".byte 0x0f, 0x01, 0xd0\n"
: "=a"(eax), "=d"(edx) : "c"(idx));
return ((uint64_t)edx << 32) | eax;
#elif _MSC_FULL_VER >= 160040219
/* Intrinsic only works on 2010 SP1 and above. */
return _xgetbv(idx);
#else
printf("Unknown compiler. Cannot check xgetbv bits.\n");
return 0;
#endif
}
#endif
#if defined(__ARM_NEON__)
static void arm_enable_runfast_mode(void)
{
/* RunFast mode. Enables flush-to-zero and some
* floating point optimizations. */
static const unsigned x = 0x04086060;
static const unsigned y = 0x03000000;
int r;
__asm__ volatile(
"fmrx %0, fpscr \n\t" /* r0 = FPSCR */
"and %0, %0, %1 \n\t" /* r0 = r0 & 0x04086060 */
"orr %0, %0, %2 \n\t" /* r0 = r0 | 0x03000000 */
"fmxr fpscr, %0 \n\t" /* FPSCR = r0 */
: "=r"(r)
: "r"(x), "r"(y)
);
}
#endif
#if defined(__linux__) && !defined(CPU_X86)
static unsigned char check_arm_cpu_feature(const char* feature)
{
char line[1024];
unsigned char status = 0;
RFILE *fp = filestream_open("/proc/cpuinfo",
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!fp)
return 0;
while (filestream_gets(fp, line, sizeof(line)) != NULL)
{
if (strncmp(line, "Features\t: ", 11))
continue;
if (strstr(line + 11, feature) != NULL)
status = 1;
break;
}
filestream_close(fp);
return status;
}
#if !defined(_SC_NPROCESSORS_ONLN)
/* Parse an decimal integer starting from 'input', but not going further
* than 'limit'. Return the value into '*result'.
*
* NOTE: Does not skip over leading spaces, or deal with sign characters.
* NOTE: Ignores overflows.
*
* The function returns NULL in case of error (bad format), or the new
* position after the decimal number in case of success (which will always
* be <= 'limit').
*/
static const char *parse_decimal(const char* input,
const char* limit, int* result)
{
const char* p = input;
int val = 0;
while (p < limit)
{
int d = (*p - '0');
if ((unsigned)d >= 10U)
break;
val = val*10 + d;
p++;
}
if (p == input)
return NULL;
*result = val;
return p;
}
/* Parse a textual list of cpus and store the result inside a CpuList object.
* Input format is the following:
* - comma-separated list of items (no spaces)
* - each item is either a single decimal number (cpu index), or a range made
* of two numbers separated by a single dash (-). Ranges are inclusive.
*
* Examples: 0
* 2,4-127,128-143
* 0-1
*/
static void cpulist_parse(CpuList* list, char **buf, ssize_t length)
{
const char* p = (const char*)buf;
const char* end = p + length;
/* NOTE: the input line coming from sysfs typically contains a
* trailing newline, so take care of it in the code below
*/
while (p < end && *p != '\n')
{
int val, start_value, end_value;
/* Find the end of current item, and put it into 'q' */
const char *q = (const char*)memchr(p, ',', end-p);
if (!q)
q = end;
/* Get first value */
p = parse_decimal(p, q, &start_value);
if (p == NULL)
return;
end_value = start_value;
/* If we're not at the end of the item, expect a dash and
* and integer; extract end value.
*/
if (p < q && *p == '-')
{
p = parse_decimal(p+1, q, &end_value);
if (p == NULL)
return;
}
/* Set bits CPU list bits */
for (val = start_value; val <= end_value; val++)
{
if ((unsigned)val < 32)
list->mask |= (uint32_t)(1U << val);
}
/* Jump to next item */
p = q;
if (p < end)
p++;
}
}
/* Read a CPU list from one sysfs file */
static void cpulist_read_from(CpuList* list, const char* filename)
{
ssize_t length;
char *buf = NULL;
list->mask = 0;
if (filestream_read_file(filename, (void**)&buf, &length) != 1)
return;
cpulist_parse(list, &buf, length);
if (buf)
free(buf);
buf = NULL;
}
#endif
#endif
/**
* cpu_features_get_core_amount:
*
* Gets the amount of available CPU cores.
*
* Returns: amount of CPU cores available.
**/
unsigned cpu_features_get_core_amount(void)
{
#if defined(_WIN32) && !defined(_XBOX)
/* Win32 */
SYSTEM_INFO sysinfo;
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
GetNativeSystemInfo(&sysinfo);
#else
GetSystemInfo(&sysinfo);
#endif
return sysinfo.dwNumberOfProcessors;
#elif defined(GEKKO)
return 1;
#elif defined(PSP) || defined(PS2)
return 1;
#elif defined(VITA)
return 4;
#elif defined(HAVE_LIBNX) || defined(SWITCH)
return 4;
#elif defined(_3DS)
u8 device_model = 0xFF;
CFGU_GetSystemModel(&device_model);/*(0 = O3DS, 1 = O3DSXL, 2 = N3DS, 3 = 2DS, 4 = N3DSXL, 5 = N2DSXL)*/
switch (device_model)
{
case 0:
case 1:
case 3:
/*Old 3/2DS*/
return 2;
case 2:
case 4:
case 5:
/*New 3/2DS*/
return 4;
default:
/*Unknown Device Or Check Failed*/
break;
}
return 1;
#elif defined(WIIU)
return 3;
#elif defined(_SC_NPROCESSORS_ONLN)
/* Linux, most UNIX-likes. */
long ret = sysconf(_SC_NPROCESSORS_ONLN);
if (ret <= 0)
return (unsigned)1;
return (unsigned)ret;
#elif defined(BSD) || defined(__APPLE__)
/* BSD */
/* Copypasta from stackoverflow, dunno if it works. */
int num_cpu = 0;
int mib[4];
size_t len = sizeof(num_cpu);
mib[0] = CTL_HW;
mib[1] = HW_AVAILCPU;
sysctl(mib, 2, &num_cpu, &len, NULL, 0);
if (num_cpu < 1)
{
mib[1] = HW_NCPU;
sysctl(mib, 2, &num_cpu, &len, NULL, 0);
if (num_cpu < 1)
num_cpu = 1;
}
return num_cpu;
#elif defined(__linux__)
CpuList cpus_present[1];
CpuList cpus_possible[1];
int amount = 0;
cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present");
cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible");
/* Compute the intersection of both sets to get the actual number of
* CPU cores that can be used on this device by the kernel.
*/
cpus_present->mask &= cpus_possible->mask;
amount = __builtin_popcount(cpus_present->mask);
if (amount == 0)
return 1;
return amount;
#elif defined(_XBOX360)
return 3;
#else
/* No idea, assume single core. */
return 1;
#endif
}
/* According to http://en.wikipedia.org/wiki/CPUID */
#define VENDOR_INTEL_b 0x756e6547
#define VENDOR_INTEL_c 0x6c65746e
#define VENDOR_INTEL_d 0x49656e69
/**
* cpu_features_get:
*
* Gets CPU features..
*
* Returns: bitmask of all CPU features available.
**/
uint64_t cpu_features_get(void)
{
int flags[4];
int vendor_shuffle[3];
char vendor[13];
size_t len = 0;
uint64_t cpu_flags = 0;
uint64_t cpu = 0;
unsigned max_flag = 0;
#if defined(CPU_X86) && !defined(__MACH__)
int vendor_is_intel = 0;
const int avx_flags = (1 << 27) | (1 << 28);
#endif
char buf[sizeof(" MMX MMXEXT SSE SSE2 SSE3 SSSE3 SS4 SSE4.2 AES AVX AVX2 NEON VMX VMX128 VFPU PS")];
memset(buf, 0, sizeof(buf));
(void)len;
(void)cpu_flags;
(void)flags;
(void)max_flag;
(void)vendor;
(void)vendor_shuffle;
#if defined(__MACH__)
len = sizeof(size_t);
if (sysctlbyname("hw.optional.mmx", NULL, &len, NULL, 0) == 0)
{
cpu |= RETRO_SIMD_MMX;
cpu |= RETRO_SIMD_MMXEXT;
}
len = sizeof(size_t);
if (sysctlbyname("hw.optional.floatingpoint", NULL, &len, NULL, 0) == 0)
{
cpu |= RETRO_SIMD_CMOV;
}
len = sizeof(size_t);
if (sysctlbyname("hw.optional.sse", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_SSE;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.sse2", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_SSE2;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.sse3", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_SSE3;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.supplementalsse3", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_SSSE3;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.sse4_1", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_SSE4;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.sse4_2", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_SSE42;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.aes", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_AES;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.avx1_0", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_AVX;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.avx2_0", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_AVX2;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.altivec", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_VMX;
len = sizeof(size_t);
if (sysctlbyname("hw.optional.neon", NULL, &len, NULL, 0) == 0)
cpu |= RETRO_SIMD_NEON;
#elif defined(_XBOX1)
cpu |= RETRO_SIMD_MMX;
cpu |= RETRO_SIMD_SSE;
cpu |= RETRO_SIMD_MMXEXT;
#elif defined(CPU_X86)
(void)avx_flags;
x86_cpuid(0, flags);
vendor_shuffle[0] = flags[1];
vendor_shuffle[1] = flags[3];
vendor_shuffle[2] = flags[2];
vendor[0] = '\0';
memcpy(vendor, vendor_shuffle, sizeof(vendor_shuffle));
/* printf("[CPUID]: Vendor: %s\n", vendor); */
vendor_is_intel = (
flags[1] == VENDOR_INTEL_b &&
flags[2] == VENDOR_INTEL_c &&
flags[3] == VENDOR_INTEL_d);
max_flag = flags[0];
if (max_flag < 1) /* Does CPUID not support func = 1? (unlikely ...) */
return 0;
x86_cpuid(1, flags);
if (flags[3] & (1 << 15))
cpu |= RETRO_SIMD_CMOV;
if (flags[3] & (1 << 23))
cpu |= RETRO_SIMD_MMX;
if (flags[3] & (1 << 25))
{
/* SSE also implies MMXEXT (according to FFmpeg source). */
cpu |= RETRO_SIMD_SSE;
cpu |= RETRO_SIMD_MMXEXT;
}
if (flags[3] & (1 << 26))
cpu |= RETRO_SIMD_SSE2;
if (flags[2] & (1 << 0))
cpu |= RETRO_SIMD_SSE3;
if (flags[2] & (1 << 9))
cpu |= RETRO_SIMD_SSSE3;
if (flags[2] & (1 << 19))
cpu |= RETRO_SIMD_SSE4;
if (flags[2] & (1 << 20))
cpu |= RETRO_SIMD_SSE42;
if ((flags[2] & (1 << 23)))
cpu |= RETRO_SIMD_POPCNT;
if (vendor_is_intel && (flags[2] & (1 << 22)))
cpu |= RETRO_SIMD_MOVBE;
if (flags[2] & (1 << 25))
cpu |= RETRO_SIMD_AES;
/* Must only perform xgetbv check if we have
* AVX CPU support (guaranteed to have at least i686). */
if (((flags[2] & avx_flags) == avx_flags)
&& ((xgetbv_x86(0) & 0x6) == 0x6))
cpu |= RETRO_SIMD_AVX;
if (max_flag >= 7)
{
x86_cpuid(7, flags);
if (flags[1] & (1 << 5))
cpu |= RETRO_SIMD_AVX2;
}
x86_cpuid(0x80000000, flags);
max_flag = flags[0];
if (max_flag >= 0x80000001u)
{
x86_cpuid(0x80000001, flags);
if (flags[3] & (1 << 23))
cpu |= RETRO_SIMD_MMX;
if (flags[3] & (1 << 22))
cpu |= RETRO_SIMD_MMXEXT;
}
#elif defined(__linux__)
if (check_arm_cpu_feature("neon"))
{
cpu |= RETRO_SIMD_NEON;
#ifdef __ARM_NEON__
arm_enable_runfast_mode();
#endif
}
if (check_arm_cpu_feature("vfpv3"))
cpu |= RETRO_SIMD_VFPV3;
if (check_arm_cpu_feature("vfpv4"))
cpu |= RETRO_SIMD_VFPV4;
if (check_arm_cpu_feature("asimd"))
{
cpu |= RETRO_SIMD_ASIMD;
#ifdef __ARM_NEON__
cpu |= RETRO_SIMD_NEON;
arm_enable_runfast_mode();
#endif
}
#if 0
check_arm_cpu_feature("swp");
check_arm_cpu_feature("half");
check_arm_cpu_feature("thumb");
check_arm_cpu_feature("fastmult");
check_arm_cpu_feature("vfp");
check_arm_cpu_feature("edsp");
check_arm_cpu_feature("thumbee");
check_arm_cpu_feature("tls");
check_arm_cpu_feature("idiva");
check_arm_cpu_feature("idivt");
#endif
#elif defined(__ARM_NEON__)
cpu |= RETRO_SIMD_NEON;
arm_enable_runfast_mode();
#elif defined(__ALTIVEC__)
cpu |= RETRO_SIMD_VMX;
#elif defined(XBOX360)
cpu |= RETRO_SIMD_VMX128;
#elif defined(PSP) || defined(PS2)
cpu |= RETRO_SIMD_VFPU;
#elif defined(GEKKO)
cpu |= RETRO_SIMD_PS;
#endif
if (cpu & RETRO_SIMD_MMX) strlcat(buf, " MMX", sizeof(buf));
if (cpu & RETRO_SIMD_MMXEXT) strlcat(buf, " MMXEXT", sizeof(buf));
if (cpu & RETRO_SIMD_SSE) strlcat(buf, " SSE", sizeof(buf));
if (cpu & RETRO_SIMD_SSE2) strlcat(buf, " SSE2", sizeof(buf));
if (cpu & RETRO_SIMD_SSE3) strlcat(buf, " SSE3", sizeof(buf));
if (cpu & RETRO_SIMD_SSSE3) strlcat(buf, " SSSE3", sizeof(buf));
if (cpu & RETRO_SIMD_SSE4) strlcat(buf, " SSE4", sizeof(buf));
if (cpu & RETRO_SIMD_SSE42) strlcat(buf, " SSE4.2", sizeof(buf));
if (cpu & RETRO_SIMD_AES) strlcat(buf, " AES", sizeof(buf));
if (cpu & RETRO_SIMD_AVX) strlcat(buf, " AVX", sizeof(buf));
if (cpu & RETRO_SIMD_AVX2) strlcat(buf, " AVX2", sizeof(buf));
if (cpu & RETRO_SIMD_NEON) strlcat(buf, " NEON", sizeof(buf));
if (cpu & RETRO_SIMD_VFPV3) strlcat(buf, " VFPv3", sizeof(buf));
if (cpu & RETRO_SIMD_VFPV4) strlcat(buf, " VFPv4", sizeof(buf));
if (cpu & RETRO_SIMD_VMX) strlcat(buf, " VMX", sizeof(buf));
if (cpu & RETRO_SIMD_VMX128) strlcat(buf, " VMX128", sizeof(buf));
if (cpu & RETRO_SIMD_VFPU) strlcat(buf, " VFPU", sizeof(buf));
if (cpu & RETRO_SIMD_PS) strlcat(buf, " PS", sizeof(buf));
if (cpu & RETRO_SIMD_ASIMD) strlcat(buf, " ASIMD", sizeof(buf));
return cpu;
}
void cpu_features_get_model_name(char *name, int len)
{
#if defined(CPU_X86) && !defined(__MACH__)
union {
int i[4];
unsigned char s[16];
} flags;
int i, j;
size_t pos = 0;
bool start = false;
if (!name)
return;
x86_cpuid(0x80000000, flags.i);
if (flags.i[0] < 0x80000004)
return;
for (i = 0; i < 3; i++)
{
memset(flags.i, 0, sizeof(flags.i));
x86_cpuid(0x80000002 + i, flags.i);
for (j = 0; j < sizeof(flags.s); j++)
{
if (!start && flags.s[j] == ' ')
continue;
else
start = true;
if (pos == len - 1)
{
/* truncate if we ran out of room */
name[pos] = '\0';
goto end;
}
name[pos++] = flags.s[j];
}
}
end:
/* terminate our string */
if (pos < (size_t)len)
name[pos] = '\0';
#elif defined(__MACH__)
if (!name)
return;
{
size_t len_size = len;
sysctlbyname("machdep.cpu.brand_string", name, &len_size, NULL, 0);
}
#else
if (!name)
return;
return;
#endif
}

View File

@@ -0,0 +1,18 @@
Copyright (c) 2013 Mikko Mononen memon@inside.org
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/header.hpp>
namespace brls
{
Header::Header(std::string label, bool separator, std::string sublabel)
: label(label)
, sublabel(sublabel)
, separator(separator)
{
Style* style = Application::getStyle();
this->setHeight(style->Header.height);
}
void Header::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
unsigned padding = style->Header.padding;
// Rectangle
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->headerRectangleColor));
nvgRect(vg, x, y + padding, style->Header.rectangleWidth, height - padding * 2);
nvgFill(vg);
// Label
nvgBeginPath(vg);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, style->Header.fontSize);
nvgFillColor(vg, a(ctx->theme->textColor));
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgText(vg, x + style->Header.rectangleWidth + padding, y + height / 2, this->label.c_str(), nullptr);
// Sublabel
if (this->sublabel != "")
{
nvgBeginPath(vg);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, style->Header.fontSize);
nvgFillColor(vg, a(ctx->theme->descriptionColor));
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
nvgText(vg, x + width - style->Header.rectangleWidth - padding, y + height / 2, this->sublabel.c_str(), nullptr);
}
// Separator
if (this->separator)
{
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->listItemSeparatorColor));
nvgRect(vg, x, y + height, width, 1);
nvgFill(vg);
}
}
} // namespace brls

View File

@@ -0,0 +1,224 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2020 WerWolv
Copyright (C) 2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/actions.hpp>
#include <borealis/application.hpp>
#include <borealis/hint.hpp>
#include <borealis/label.hpp>
#include <set>
namespace brls
{
Hint::Hint(bool animate)
: BoxLayout(BoxLayoutOrientation::HORIZONTAL)
, animate(animate)
{
Style* style = Application::getStyle();
this->setGravity(BoxLayoutGravity::RIGHT);
this->setHeight(style->AppletFrame.footerHeight);
this->setSpacing(style->AppletFrame.footerTextSpacing);
// Subscribe to all events
this->globalFocusEventSubscriptor = Application::getGlobalFocusChangeEvent()->subscribe([this](View* newFocus) {
this->rebuildHints();
});
this->globalHintsUpdateEventSubscriptor = Application::getGlobalHintsUpdateEvent()->subscribe([this]() {
this->rebuildHints();
});
}
bool actionsSortFunc(Action a, Action b)
{
// From left to right:
// - first +
// - then all hints that are not B and A
// - finally B and A
// + is before all others
if (a.key == Key::PLUS)
return true;
// A is after all others
if (b.key == Key::A)
return true;
// B is after all others but A
if (b.key == Key::B && a.key != Key::A)
return true;
// Keep original order for the rest
return false;
}
void Hint::rebuildHints()
{
// Check if the focused element is still a child of the same parent as the hint view's
{
View* focusParent = Application::getCurrentFocus();
View* hintBaseParent = this;
while (focusParent != nullptr)
{
if (focusParent->getParent() == nullptr)
break;
focusParent = focusParent->getParent();
}
while (hintBaseParent != nullptr)
{
if (hintBaseParent->getParent() == nullptr)
break;
hintBaseParent = hintBaseParent->getParent();
}
if (focusParent != hintBaseParent)
return;
}
// Empty the layout and re-populate it with new Labels
this->clear(true);
std::set<Key> addedKeys; // we only ever want one action per key
View* focusParent = Application::getCurrentFocus();
// Iterate over the view tree to find all the actions to display
std::vector<Action> actions;
while (focusParent != nullptr)
{
for (auto& action : focusParent->getActions())
{
if (action.hidden)
continue;
if (addedKeys.find(action.key) != addedKeys.end())
continue;
addedKeys.insert(action.key);
actions.push_back(action);
}
focusParent = focusParent->getParent();
}
// Sort the actions
std::stable_sort(actions.begin(), actions.end(), actionsSortFunc);
// Populate the layout with labels
for (Action action : actions)
{
std::string hintText = Hint::getKeyIcon(action.key) + " " + action.hintText;
Label* label = new Label(LabelStyle::HINT, hintText);
this->addView(label);
}
}
Hint::~Hint()
{
// Unregister all events
Application::getGlobalFocusChangeEvent()->unsubscribe(this->globalFocusEventSubscriptor);
Application::getGlobalHintsUpdateEvent()->unsubscribe(this->globalHintsUpdateEventSubscriptor);
}
std::string Hint::getKeyIcon(Key key)
{
switch (key)
{
case Key::A:
return "\uE0E0";
case Key::B:
return "\uE0E1";
case Key::X:
return "\uE0E2";
case Key::Y:
return "\uE0E3";
case Key::LSTICK:
return "\uE104";
case Key::RSTICK:
return "\uE105";
case Key::L:
return "\uE0E4";
case Key::R:
return "\uE0E5";
case Key::PLUS:
return "\uE0EF";
case Key::MINUS:
return "\uE0F0";
case Key::DLEFT:
return "\uE0ED";
case Key::DUP:
return "\uE0EB";
case Key::DRIGHT:
return "\uE0EF";
case Key::DDOWN:
return "\uE0EC";
default:
return "\uE152";
}
}
void Hint::willAppear(bool resetState)
{
// Push ourself to hide other hints
// We assume that we are the top-most hint
Hint::pushHint(this);
}
void Hint::willDisappear(bool resetState)
{
// Pop ourself to show other hints
Hint::popHint(this);
}
void Hint::pushHint(Hint* hint)
{
// If the hint is already in the stack, remove it and put it back on top
Hint::globalHintStack.erase(std::remove(Hint::globalHintStack.begin(), Hint::globalHintStack.end(), hint), Hint::globalHintStack.end());
Hint::globalHintStack.push_back(hint);
//Trigger animation
Hint::animateHints();
}
void Hint::popHint(Hint* hint)
{
Hint::globalHintStack.erase(std::remove(Hint::globalHintStack.begin(), Hint::globalHintStack.end(), hint), Hint::globalHintStack.end());
Hint::animateHints();
// animateHints() won't call hide() on the hint since it's been removed from the stack
// but it doesn't matter since it is getting destroyed anyay
}
void Hint::animateHints()
{
for (size_t i = 0; i < Hint::globalHintStack.size(); i++)
{
// Last one = top one: show
if (i == Hint::globalHintStack.size() - 1)
Hint::globalHintStack[i]->show([]() {}, false);
else
Hint::globalHintStack[i]->hide([]() {}, false);
}
}
} // namespace brls

View File

@@ -0,0 +1,183 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/image.hpp>
#include <cstring>
namespace brls
{
Image::Image(std::string imagePath)
{
this->setImage(imagePath);
this->setOpacity(1.0F);
}
Image::Image(unsigned char* buffer, size_t bufferSize)
{
this->setImage(buffer, bufferSize);
this->setOpacity(1.0F);
}
Image::~Image()
{
if (this->imageBuffer != nullptr)
delete[] this->imageBuffer;
if (this->texture != -1)
nvgDeleteImage(Application::getNVGContext(), this->texture);
}
void Image::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
nvgSave(vg);
if (this->texture != -1)
{
nvgBeginPath(vg);
nvgRoundedRect(vg, x + this->imageX, y + this->imageY, this->imageWidth, this->imageHeight, this->cornerRadius);
nvgFillPaint(vg, a(this->imgPaint));
nvgFill(vg);
}
nvgRestore(vg);
}
void Image::reloadTexture()
{
NVGcontext* vg = Application::getNVGContext();
if (this->texture != -1)
nvgDeleteImage(vg, this->texture);
if (!this->imagePath.empty())
this->texture = nvgCreateImage(vg, this->imagePath.c_str(), 0);
else if (this->imageBuffer != nullptr)
this->texture = nvgCreateImageMem(vg, 0, this->imageBuffer, this->imageBufferSize);
}
void Image::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
if (this->origViewWidth == 0 || this->origViewHeight == 0)
{
this->origViewWidth = this->getWidth();
this->origViewHeight = this->getHeight();
}
nvgImageSize(vg, this->texture, &this->imageWidth, &this->imageHeight);
this->setWidth(this->origViewWidth);
this->setHeight(this->origViewHeight);
this->imageX = 0;
this->imageY = 0;
float viewAspectRatio = static_cast<float>(this->getWidth()) / static_cast<float>(this->getHeight());
float imageAspectRatio = static_cast<float>(this->imageWidth) / static_cast<float>(this->imageHeight);
switch (imageScaleType)
{
case ImageScaleType::NO_RESIZE:
this->imageX = (this->origViewWidth - this->imageWidth) / 2.0F;
this->imageY = (this->origViewHeight - this->imageHeight) / 2.0F;
break;
case ImageScaleType::FIT:
if (viewAspectRatio >= imageAspectRatio)
{
this->imageHeight = this->getHeight();
this->imageWidth = this->imageHeight * imageAspectRatio;
this->imageX = (this->origViewWidth - this->imageWidth) / 2.0F;
}
else
{
this->imageWidth = this->getWidth();
this->imageHeight = this->imageWidth * imageAspectRatio;
this->imageY = (this->origViewHeight - this->imageHeight) / 2.0F;
}
break;
case ImageScaleType::CROP:
if (viewAspectRatio < imageAspectRatio)
{
this->imageHeight = this->getHeight();
this->imageWidth = this->imageHeight * imageAspectRatio;
this->imageX = (this->origViewWidth - this->imageWidth) / 2.0F;
}
else
{
this->imageWidth = this->getWidth();
this->imageHeight = this->imageWidth * imageAspectRatio;
this->imageY = (this->origViewHeight - this->imageHeight) / 2.0F;
}
break;
case ImageScaleType::SCALE:
this->imageWidth = this->getWidth();
this->imageHeight = this->getHeight();
break;
case ImageScaleType::VIEW_RESIZE:
this->setWidth(this->imageWidth);
this->setHeight(this->imageHeight);
break;
}
this->imgPaint = nvgImagePattern(vg, getX() + this->imageX, getY() + this->imageY, this->imageWidth, this->imageHeight, 0, this->texture, this->alpha);
}
void Image::setImage(unsigned char* buffer, size_t bufferSize)
{
if (this->imageBuffer != nullptr)
delete[] this->imageBuffer;
this->imagePath = "";
this->imageBuffer = new unsigned char[bufferSize];
std::memcpy(this->imageBuffer, buffer, bufferSize);
this->imageBufferSize = bufferSize;
this->reloadTexture();
this->invalidate();
}
void Image::setImage(std::string imagePath)
{
this->imagePath = imagePath;
if (this->imageBuffer != nullptr)
delete[] this->imageBuffer;
this->imageBuffer = nullptr;
this->reloadTexture();
this->invalidate();
}
void Image::setOpacity(float opacity)
{
this->alpha = opacity;
this->invalidate();
}
void Image::setScaleType(ImageScaleType imageScaleType)
{
this->imageScaleType = imageScaleType;
this->invalidate();
}
} // namespace brls

View File

@@ -0,0 +1,229 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/label.hpp>
namespace brls
{
Label::Label(LabelStyle labelStyle, std::string text, bool multiline)
: text(text)
, multiline(multiline)
, labelStyle(labelStyle)
{
Style* style = Application::getStyle();
this->lineHeight = style->Label.lineHeight;
switch (labelStyle)
{
case LabelStyle::REGULAR:
this->fontSize = style->Label.regularFontSize;
break;
case LabelStyle::MEDIUM:
this->fontSize = style->Label.mediumFontSize;
break;
case LabelStyle::SMALL:
this->fontSize = style->Label.smallFontSize;
break;
case LabelStyle::DESCRIPTION:
this->fontSize = style->Label.descriptionFontSize;
break;
case LabelStyle::CRASH:
this->fontSize = style->Label.crashFontSize;
break;
case LabelStyle::BUTTON_PLAIN_DISABLED:
case LabelStyle::BUTTON_PLAIN:
case LabelStyle::BUTTON_BORDERLESS:
case LabelStyle::BUTTON_DIALOG:
this->fontSize = style->Label.buttonFontSize;
break;
case LabelStyle::LIST_ITEM:
this->fontSize = style->Label.listItemFontSize;
break;
case LabelStyle::NOTIFICATION:
this->fontSize = style->Label.notificationFontSize;
this->lineHeight = style->Label.notificationLineHeight;
break;
case LabelStyle::DIALOG:
this->fontSize = style->Label.dialogFontSize;
break;
case LabelStyle::HINT:
this->fontSize = style->Label.hintFontSize;
}
}
void Label::setHorizontalAlign(NVGalign align)
{
this->horizontalAlign = align;
}
void Label::setVerticalAlign(NVGalign align)
{
this->verticalAlign = align;
}
void Label::setFontSize(unsigned size)
{
this->fontSize = size;
if (this->getParent())
this->getParent()->invalidate();
}
void Label::setText(std::string text)
{
this->text = text;
if (this->hasParent())
this->getParent()->invalidate();
}
void Label::setStyle(LabelStyle style)
{
this->labelStyle = style;
}
void Label::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
nvgSave(vg);
nvgReset(vg);
nvgFontSize(vg, this->fontSize);
nvgTextAlign(vg, this->horizontalAlign | NVG_ALIGN_TOP);
nvgFontFaceId(vg, this->getFont(stash));
nvgTextLineHeight(vg, this->lineHeight);
float bounds[4];
// Update width or height to text bounds
if (this->multiline)
{
nvgTextBoxBounds(vg, this->x, this->y, this->width, this->text.c_str(), nullptr, bounds);
this->height = bounds[3] - bounds[1]; // ymax - ymin
}
else
{
nvgTextBounds(vg, this->x, this->y, this->text.c_str(), nullptr, bounds);
unsigned oldWidth = this->width;
this->width = bounds[2] - bounds[0]; // xmax - xmin
// offset the position to compensate the width change
// and keep right alignment
if (this->horizontalAlign == NVG_ALIGN_RIGHT)
this->x += oldWidth - this->width;
}
nvgRestore(vg);
}
void Label::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
nvgFillColor(vg, this->getColor(ctx->theme));
// Draw
nvgFontSize(vg, this->fontSize);
nvgFontFaceId(vg, this->getFont(ctx->fontStash));
if (this->multiline)
{
nvgTextLineHeight(vg, this->lineHeight);
nvgTextAlign(vg, this->horizontalAlign | NVG_ALIGN_TOP);
nvgBeginPath(vg);
nvgTextBox(vg, x, y, width, this->text.c_str(), nullptr);
}
else
{
nvgTextLineHeight(vg, 1.0f);
nvgTextAlign(vg, this->horizontalAlign | this->verticalAlign);
nvgBeginPath(vg);
if (this->horizontalAlign == NVG_ALIGN_RIGHT)
x += width;
else if (this->horizontalAlign == NVG_ALIGN_CENTER)
x += width / 2;
// TODO: Ticker
if (this->verticalAlign == NVG_ALIGN_BOTTOM)
nvgText(vg, x, y + height, this->text.c_str(), nullptr);
else
nvgText(vg, x, y + height / 2, this->text.c_str(), nullptr);
}
}
void Label::setColor(NVGcolor color)
{
this->customColor = color;
this->useCustomColor = true;
}
void Label::unsetColor()
{
this->useCustomColor = false;
}
NVGcolor Label::getColor(ThemeValues* theme)
{
// Use custom color if any
if (this->useCustomColor)
return a(this->customColor);
switch (this->labelStyle)
{
case LabelStyle::DESCRIPTION:
return a(theme->descriptionColor);
case LabelStyle::CRASH:
return RGB(255, 255, 255);
case LabelStyle::BUTTON_PLAIN:
return a(theme->buttonPlainEnabledTextColor);
case LabelStyle::BUTTON_PLAIN_DISABLED:
return a(theme->buttonPlainDisabledTextColor);
case LabelStyle::NOTIFICATION:
return a(theme->notificationTextColor);
case LabelStyle::BUTTON_DIALOG:
return a(theme->dialogButtonColor);
default:
return a(theme->textColor);
}
}
void Label::setFont(int font)
{
this->customFont = font;
this->useCustomFont = true;
}
void Label::unsetFont()
{
this->useCustomFont = false;
}
int Label::getFont(FontStash* stash)
{
if (this->useCustomFont)
return this->customFont;
return stash->regular;
}
} // namespace brls

View File

@@ -0,0 +1,127 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 WerWolv
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/layer_view.hpp>
namespace brls
{
LayerView::LayerView()
{
}
LayerView::~LayerView()
{
for (unsigned int i = 0; i < this->layers.size(); i++)
delete this->layers[i];
this->layers.clear();
}
void LayerView::addLayer(View* view)
{
if (view)
{
view->setParent(this);
this->layers.push_back(view);
}
}
void LayerView::changeLayer(int index, bool focus)
{
if (index >= 0 && index < static_cast<int>(this->layers.size()))
{
Application::blockInputs();
if (this->selectedIndex >= 0)
{
this->layers[this->selectedIndex]->willDisappear(true);
this->layers[this->selectedIndex]->hide([]() {});
}
this->selectedIndex = index;
this->layers[this->selectedIndex]->willAppear(true);
this->layers[this->selectedIndex]->show([=]() {
if (focus)
Application::giveFocus(this->layers[this->selectedIndex]->getDefaultFocus());
Application::unblockInputs();
});
this->layers[index]->invalidate();
}
if (index == -1)
{
if (this->selectedIndex > 0)
{
this->layers[this->selectedIndex]->willDisappear(true);
this->layers[this->selectedIndex]->hide([]() {});
}
this->selectedIndex = index;
}
}
int LayerView::getLayerIndex()
{
return this->selectedIndex;
}
View* LayerView::getDefaultFocus()
{
if (this->selectedIndex >= 0 && this->selectedIndex < static_cast<int>(this->layers.size()))
{
View* newFocus = this->layers[this->selectedIndex]->getDefaultFocus();
if (newFocus)
{
return newFocus;
}
}
return nullptr;
}
void LayerView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
if (this->selectedIndex >= 0 && this->selectedIndex < static_cast<int>(this->layers.size()))
this->layers[this->selectedIndex]->frame(ctx);
}
void LayerView::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
if (this->selectedIndex >= 0 && this->selectedIndex < static_cast<int>(this->layers.size()))
{
this->layers[this->selectedIndex]->setBoundaries(this->getX(), this->getY(), this->getWidth(), this->getHeight());
this->layers[this->selectedIndex]->invalidate();
}
}
void LayerView::willAppear(bool resetState)
{
if (this->selectedIndex >= 0 && this->selectedIndex < static_cast<int>(this->layers.size()))
this->layers[this->selectedIndex]->willAppear(true);
}
void LayerView::willDisappear(bool resetState)
{
if (this->selectedIndex >= 0 && this->selectedIndex < static_cast<int>(this->layers.size()))
this->layers[this->selectedIndex]->willDisappear(true);
}
}

View File

@@ -0,0 +1,623 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/dropdown.hpp>
#include <borealis/header.hpp>
#include <borealis/list.hpp>
#include <borealis/logger.hpp>
#include <borealis/swkbd.hpp>
#include <borealis/table.hpp>
// TODO: Scrollbar
namespace brls
{
ListContentView::ListContentView(List* list, size_t defaultFocus)
: BoxLayout(BoxLayoutOrientation::VERTICAL, defaultFocus)
, list(list)
{
Style* style = Application::getStyle();
this->setMargins(style->List.marginTopBottom, style->List.marginLeftRight, style->List.marginTopBottom, style->List.marginLeftRight);
this->setSpacing(style->List.spacing);
this->setRememberFocus(true);
}
void ListContentView::customSpacing(View* current, View* next, int* spacing)
{
// Don't add spacing to the first list item
// if it doesn't have a description and the second one is a
// list item too
// Or if the next item is a ListItemGroupSpacing
if (ListItem* currentItem = dynamic_cast<ListItem*>(current))
{
if (currentItem->getReduceDescriptionSpacing())
{
if (next != nullptr)
*spacing /= 2;
}
else if (ListItem* nextItem = dynamic_cast<ListItem*>(next))
{
if (!currentItem->hasDescription())
{
*spacing = 2;
nextItem->setDrawTopSeparator(currentItem->isCollapsed());
}
}
else if (dynamic_cast<ListItemGroupSpacing*>(next))
{
*spacing = 0;
}
else if (dynamic_cast<Table*>(next))
{
*spacing /= 2;
}
}
// Table custom spacing
else if (dynamic_cast<Table*>(current))
{
*spacing /= 2;
}
// ListItemGroupSpacing custom spacing
else if (dynamic_cast<ListItemGroupSpacing*>(current))
{
*spacing /= 2;
}
// Header custom spacing
else if (dynamic_cast<Header*>(current) || dynamic_cast<Header*>(next))
{
if (dynamic_cast<Header*>(current) && dynamic_cast<ListItem*>(next))
{
*spacing = 1;
}
else if (dynamic_cast<Label*>(current) && dynamic_cast<Header*>(next))
{
// Keep default spacing
}
else
{
Style* style = Application::getStyle();
*spacing = style->Header.padding;
}
}
// Call list custom spacing
if (this->list)
this->list->customSpacing(current, next, spacing);
}
ListItem::ListItem(std::string label, std::string description, std::string subLabel)
: label(label)
, subLabel(subLabel)
{
Style* style = Application::getStyle();
this->setHeight(subLabel != "" ? style->List.Item.heightWithSubLabel : style->List.Item.height);
this->setTextSize(style->Label.listItemFontSize);
if (description != "")
{
this->descriptionView = new Label(LabelStyle::DESCRIPTION, description, true);
this->descriptionView->setParent(this);
}
this->registerAction("OK", Key::A, [this] { return this->onClick(); });
}
void ListItem::setThumbnail(Image* image)
{
if (this->thumbnailView)
delete this->thumbnailView;
if (image != NULL)
{
this->thumbnailView = image;
this->thumbnailView->setParent(this);
this->invalidate();
}
}
void ListItem::setThumbnail(std::string imagePath)
{
if (this->thumbnailView)
this->thumbnailView->setImage(imagePath);
else
this->thumbnailView = new Image(imagePath);
this->thumbnailView->setParent(this);
this->thumbnailView->setScaleType(ImageScaleType::FIT);
this->invalidate();
}
void ListItem::setThumbnail(unsigned char* buffer, size_t bufferSize)
{
if (this->thumbnailView)
this->thumbnailView->setImage(buffer, bufferSize);
else
this->thumbnailView = new Image(buffer, bufferSize);
this->thumbnailView->setParent(this);
this->thumbnailView->setScaleType(ImageScaleType::FIT);
this->invalidate();
}
bool ListItem::getReduceDescriptionSpacing()
{
return this->reduceDescriptionSpacing;
}
void ListItem::setReduceDescriptionSpacing(bool value)
{
this->reduceDescriptionSpacing = value;
}
void ListItem::setIndented(bool indented)
{
this->indented = indented;
}
void ListItem::setTextSize(unsigned textSize)
{
this->textSize = textSize;
}
void ListItem::setChecked(bool checked)
{
this->checked = checked;
}
bool ListItem::onClick()
{
return this->clickEvent.fire(this);
}
GenericEvent* ListItem::getClickEvent()
{
return &this->clickEvent;
}
void ListItem::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Description
if (this->descriptionView)
{
unsigned indent = style->List.Item.descriptionIndent;
if (this->indented)
indent += style->List.Item.indent;
this->height = style->List.Item.height;
this->descriptionView->setBoundaries(this->x + indent, this->y + this->height + style->List.Item.descriptionSpacing, this->width - indent * 2, 0);
this->descriptionView->invalidate(true); // we must call layout directly
this->height += this->descriptionView->getHeight() + style->List.Item.descriptionSpacing;
}
// Thumbnail
if (this->thumbnailView)
{
Style* style = Application::getStyle();
unsigned thumbnailSize = height - style->List.Item.thumbnailPadding * 2;
this->thumbnailView->setBoundaries(
x + style->List.Item.thumbnailPadding,
y + style->List.Item.thumbnailPadding,
thumbnailSize,
thumbnailSize);
this->thumbnailView->invalidate();
}
}
void ListItem::getHighlightInsets(unsigned* top, unsigned* right, unsigned* bottom, unsigned* left)
{
Style* style = Application::getStyle();
View::getHighlightInsets(top, right, bottom, left);
if (descriptionView)
*bottom = -(descriptionView->getHeight() + style->List.Item.descriptionSpacing);
if (indented)
*left = -style->List.Item.indent;
}
void ListItem::resetValueAnimation()
{
this->valueAnimation = 0.0f;
menu_animation_ctx_tag tag = (uintptr_t) & this->valueAnimation;
menu_animation_kill_by_tag(&tag);
}
void ListItem::setValue(std::string value, bool faint, bool animate)
{
this->oldValue = this->value;
this->oldValueFaint = this->valueFaint;
this->value = value;
this->valueFaint = faint;
this->resetValueAnimation();
if (animate && this->oldValue != "")
{
Style* style = Application::getStyle();
menu_animation_ctx_tag tag = (uintptr_t) & this->valueAnimation;
menu_animation_ctx_entry_t entry;
entry.cb = [this](void* userdata) { this->resetValueAnimation(); };
entry.duration = style->AnimationDuration.highlight;
entry.easing_enum = EASING_IN_OUT_QUAD;
entry.subject = &this->valueAnimation;
entry.tag = tag;
entry.target_value = 1.0f;
entry.tick = [](void* userdata) {};
entry.userdata = nullptr;
menu_animation_push(&entry);
}
}
std::string ListItem::getValue()
{
return this->value;
}
void ListItem::setDrawTopSeparator(bool draw)
{
this->drawTopSeparator = draw;
}
View* ListItem::getDefaultFocus()
{
if (this->collapseState != 1.0f)
return nullptr;
return this;
}
void ListItem::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
unsigned baseHeight = this->height;
bool hasSubLabel = this->subLabel != "";
bool hasThumbnail = this->thumbnailView;
unsigned leftPadding = hasThumbnail ? this->thumbnailView->getWidth() + style->List.Item.thumbnailPadding * 2 : style->List.Item.padding;
if (this->indented)
{
x += style->List.Item.indent;
width -= style->List.Item.indent;
}
// Description
if (this->descriptionView)
{
// Don't count description as part of list item
baseHeight -= this->descriptionView->getHeight() + style->List.Item.descriptionSpacing;
this->descriptionView->frame(ctx);
}
// Value
unsigned valueX = x + width - style->List.Item.padding;
unsigned valueY = y + (hasSubLabel ? baseHeight - baseHeight / 3 : baseHeight / 2);
nvgTextAlign(vg, NVG_ALIGN_RIGHT | (hasSubLabel ? NVG_ALIGN_TOP : NVG_ALIGN_MIDDLE));
nvgFontFaceId(vg, ctx->fontStash->regular);
if (this->valueAnimation != 0.0f)
{
//Old value
NVGcolor valueColor = a(this->oldValueFaint ? ctx->theme->listItemFaintValueColor : ctx->theme->listItemValueColor);
valueColor.a *= (1.0f - this->valueAnimation);
nvgFillColor(vg, valueColor);
nvgFontSize(vg, style->List.Item.valueSize * (1.0f - this->valueAnimation));
nvgBeginPath(vg);
nvgText(vg, valueX, valueY, this->oldValue.c_str(), nullptr);
//New value
valueColor = a(this->valueFaint ? ctx->theme->listItemFaintValueColor : ctx->theme->listItemValueColor);
valueColor.a *= this->valueAnimation;
nvgFillColor(vg, valueColor);
nvgFontSize(vg, style->List.Item.valueSize * this->valueAnimation);
nvgBeginPath(vg);
nvgText(vg, valueX, valueY, this->value.c_str(), nullptr);
}
else
{
nvgFillColor(vg, a(this->valueFaint ? ctx->theme->listItemFaintValueColor : ctx->theme->listItemValueColor));
nvgFontSize(vg, style->List.Item.valueSize);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgBeginPath(vg);
nvgText(vg, valueX, valueY, this->value.c_str(), nullptr);
}
// Checked marker
if (this->checked)
{
unsigned radius = style->List.Item.selectRadius;
unsigned centerX = x + width - radius - style->List.Item.padding;
unsigned centerY = y + baseHeight / 2;
float radiusf = (float)radius;
int thickness = roundf(radiusf * 0.10f);
// Background
nvgFillColor(vg, a(ctx->theme->listItemValueColor));
nvgBeginPath(vg);
nvgCircle(vg, centerX, centerY, radiusf);
nvgFill(vg);
// Check mark
nvgFillColor(vg, a(ctx->theme->backgroundColorRGB));
// Long stroke
nvgSave(vg);
nvgTranslate(vg, centerX, centerY);
nvgRotate(vg, -NVG_PI / 4.0f);
nvgBeginPath(vg);
nvgRect(vg, -(radiusf * 0.55f), 0, radiusf * 1.3f, thickness);
nvgFill(vg);
nvgRestore(vg);
// Short stroke
nvgSave(vg);
nvgTranslate(vg, centerX - (radiusf * 0.65f), centerY);
nvgRotate(vg, NVG_PI / 4.0f);
nvgBeginPath(vg);
nvgRect(vg, 0, -(thickness / 2), radiusf * 0.53f, thickness);
nvgFill(vg);
nvgRestore(vg);
}
// Label
nvgFillColor(vg, a(ctx->theme->textColor));
nvgFontSize(vg, this->textSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgBeginPath(vg);
nvgText(vg, x + leftPadding, y + baseHeight / (hasSubLabel ? 3 : 2), this->label.c_str(), nullptr);
// Sub Label
if (hasSubLabel)
{
nvgFillColor(vg, a(ctx->theme->descriptionColor));
nvgFontSize(vg, style->Label.descriptionFontSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgBeginPath(vg);
nvgText(vg, x + leftPadding, y + baseHeight - baseHeight / 3, this->subLabel.c_str(), nullptr);
}
// Thumbnail
if (hasThumbnail)
this->thumbnailView->frame(ctx);
// Separators
// Offset by one to be hidden by highlight
nvgFillColor(vg, a(ctx->theme->listItemSeparatorColor));
// Top
if (this->drawTopSeparator)
{
nvgBeginPath(vg);
nvgRect(vg, x, y - 1, width, 1);
nvgFill(vg);
}
// Bottom
nvgBeginPath(vg);
nvgRect(vg, x, y + 1 + baseHeight, width, 1);
nvgFill(vg);
}
bool ListItem::hasDescription()
{
return this->descriptionView;
}
std::string ListItem::getLabel()
{
return this->label;
}
ListItem::~ListItem()
{
if (this->descriptionView)
delete this->descriptionView;
if (this->thumbnailView)
delete this->thumbnailView;
this->resetValueAnimation();
}
ToggleListItem::ToggleListItem(std::string label, bool initialValue, std::string description, std::string onValue, std::string offValue)
: ListItem(label, description)
, toggleState(initialValue)
, onValue(onValue)
, offValue(offValue)
{
this->updateValue();
}
void ToggleListItem::updateValue()
{
if (this->toggleState)
this->setValue(this->onValue, false);
else
this->setValue(this->offValue, true);
}
bool ToggleListItem::onClick()
{
this->toggleState = !this->toggleState;
this->updateValue();
ListItem::onClick();
return true;
}
bool ToggleListItem::getToggleState()
{
return this->toggleState;
}
InputListItem::InputListItem(std::string label, std::string initialValue, std::string helpText, std::string description, int maxInputLength)
: ListItem(label, description)
, helpText(helpText)
, maxInputLength(maxInputLength)
{
this->setValue(initialValue, false);
}
bool InputListItem::onClick()
{
Swkbd::openForText([&](std::string text) {
this->setValue(text, false);
},
this->helpText, "", this->maxInputLength, this->getValue());
ListItem::onClick();
return true;
}
IntegerInputListItem::IntegerInputListItem(std::string label, int initialValue, std::string helpText, std::string description, int maxInputLength)
: InputListItem(label, std::to_string(initialValue), helpText, description, maxInputLength)
{
}
bool IntegerInputListItem::onClick()
{
Swkbd::openForNumber([&](int number) {
this->setValue(std::to_string(number), false);
},
this->helpText, "", this->maxInputLength, this->getValue());
ListItem::onClick();
return true;
}
ListItemGroupSpacing::ListItemGroupSpacing(bool separator)
: Rectangle(nvgRGBA(0, 0, 0, 0))
{
ThemeValues* theme = Application::getThemeValues();
if (separator)
this->setColor(theme->listItemSeparatorColor);
}
SelectListItem::SelectListItem(std::string label, std::vector<std::string> values, unsigned selectedValue)
: ListItem(label, "")
, values(values)
, selectedValue(selectedValue)
{
this->setValue(values[selectedValue], false, false);
this->getClickEvent()->subscribe([this](View* view) {
ValueSelectedEvent::Callback valueCallback = [this](int result) {
if (result == -1)
return;
this->setValue(this->values[result], false, false);
this->selectedValue = result;
this->valueEvent.fire(result);
};
Dropdown::open(this->getLabel(), this->values, valueCallback, this->selectedValue);
});
}
void SelectListItem::setSelectedValue(unsigned value)
{
if (value >= 0 && value < this->values.size())
{
this->selectedValue = value;
this->setValue(this->values[value], false, false);
}
}
ValueSelectedEvent* SelectListItem::getValueSelectedEvent()
{
return &this->valueEvent;
}
List::List(size_t defaultFocus)
{
this->layout = new ListContentView(this, defaultFocus);
this->layout->setResize(true);
this->layout->setParent(this);
this->setContentView(this->layout);
}
// Wrapped BoxLayout methods
void List::addView(View* view, bool fill)
{
this->layout->addView(view, fill);
}
void List::clear(bool free)
{
this->layout->clear(free);
}
void List::setMargins(unsigned top, unsigned right, unsigned bottom, unsigned left)
{
this->layout->setMargins(
top,
right,
bottom,
left);
}
void List::setSpacing(unsigned spacing)
{
this->layout->setSpacing(spacing);
}
unsigned List::getSpacing()
{
return this->layout->getSpacing();
}
void List::setMarginBottom(unsigned bottom)
{
this->layout->setMarginBottom(bottom);
}
void List::customSpacing(View* current, View* next, int* spacing)
{
// Nothing to do by default
}
List::~List()
{
// ScrollView already deletes the content view
}
} // namespace brls

View File

@@ -0,0 +1,73 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <stdio.h>
#include <borealis/logger.hpp>
namespace brls
{
static LogLevel g_logLevel = LogLevel::INFO;
void Logger::setLogLevel(LogLevel newLogLevel)
{
g_logLevel = newLogLevel;
}
void Logger::log(LogLevel logLevel, const char* prefix, const char* color, const char* format, va_list ap)
{
if (g_logLevel < logLevel)
return;
printf("\033%s[%s]\033[0m ", color, prefix);
vprintf(format, ap);
printf("\n");
#ifdef __MINGW32__
fflush(0);
#endif
}
void Logger::error(const char* format, ...)
{
va_list ap;
va_start(ap, format);
Logger::log(LogLevel::ERROR, "ERROR", "[0;31m", format, ap);
va_end(ap);
}
void Logger::info(const char* format, ...)
{
va_list ap;
va_start(ap, format);
Logger::log(LogLevel::INFO, "INFO", "[0;34m", format, ap);
va_end(ap);
}
void Logger::debug(const char* format, ...)
{
va_list ap;
va_start(ap, format);
Logger::log(LogLevel::DEBUG, "DEBUG", "[0;32m", format, ap);
va_end(ap);
}
} // namespace brls

View File

@@ -0,0 +1,48 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/material_icon.hpp>
namespace brls
{
MaterialIcon::MaterialIcon(std::string icon)
: icon(icon)
{
}
void MaterialIcon::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
NVGcolor color = a(ctx->theme->textColor);
nvgTextLineHeight(vg, 1.0f);
nvgFillColor(vg, color);
nvgFontSize(vg, height);
nvgFontFaceId(vg, ctx->fontStash->material);
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, this->middleX, this->middleY, this->icon.c_str(), nullptr);
}
void MaterialIcon::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
this->middleX = this->getX() + this->getWidth() / 2;
this->middleY = this->getY() + this->getHeight() / 2;
}
}; // namespace brls

View File

@@ -0,0 +1,205 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/logger.hpp>
#include <borealis/notification_manager.hpp>
#include <cstring>
// TODO: add a timeout duration enum for longer notifications
namespace brls
{
NotificationManager::NotificationManager()
{
std::memset(this->notifications, 0, sizeof(Notification*) * BRLS_NOTIFICATIONS_MAX);
}
void NotificationManager::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
for (size_t i = 0; i < BRLS_NOTIFICATIONS_MAX; i++)
{
if (this->notifications[i])
{
Notification* notification = this->notifications[i];
float alpha = notification->getAlpha();
float translation = 0.0f;
if (alpha != 1.0f)
{
translation = (1.0f - alpha) * (float)style->Notification.slideAnimation;
nvgTranslate(vg, translation, 0);
}
this->notifications[i]->frame(ctx);
if (alpha != 1.0f)
nvgTranslate(vg, -translation, 0);
}
}
}
void NotificationManager::notify(std::string text)
{
// Find the first available notification slot
bool found = false;
size_t i = 0;
for (i = 0; i < BRLS_NOTIFICATIONS_MAX; i++)
{
if (!this->notifications[i])
{
found = true;
break;
}
}
if (!found)
{
brls::Logger::info("Discarding notification \"%s\"", text.c_str());
return;
}
// Create the notification
brls::Logger::info("Showing notification \"%s\"", text.c_str());
Notification* notification = new Notification(text);
notification->setParent(this);
notification->show([]() {});
// Timeout timer
menu_timer_ctx_entry_t entry;
entry.duration = Application::getStyle()->AnimationDuration.notificationTimeout;
entry.tick = [](void*) {};
entry.userdata = nullptr;
entry.cb = [this, notification, i](void* userdata) {
notification->hide([this, notification, i]() {
delete notification;
this->notifications[i] = nullptr;
});
};
menu_timer_start(&notification->timeoutTimer, &entry);
this->notifications[i] = notification;
// Layout the notification
this->layoutNotification(i);
}
void NotificationManager::layoutNotification(size_t index)
{
Notification* notification = this->notifications[index];
if (!notification)
return;
Style* style = Application::getStyle();
// Get the position of the last notification
Notification* lastNotification = nullptr;
for (size_t i = 0; i < index; i++)
{
if (this->notifications[i])
lastNotification = this->notifications[i];
}
unsigned y = lastNotification ? (lastNotification->getY() + lastNotification->getHeight()) : 0;
// Layout the notification
unsigned width = style->Notification.width;
notification->setBoundaries(
this->getX() + this->getWidth() - width,
this->getY() + y,
width,
0 // height is dynamic
);
notification->invalidate(); // TODO: call layout directly to fix posting multiple notifications in one frame
}
void NotificationManager::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
for (size_t i = 0; i < BRLS_NOTIFICATIONS_MAX; i++)
{
if (this->notifications[i])
this->layoutNotification(i);
}
}
NotificationManager::~NotificationManager()
{
for (size_t i = 0; i < BRLS_NOTIFICATIONS_MAX; i++)
{
if (this->notifications[i])
delete this->notifications[i];
}
}
Notification::Notification(std::string text)
{
this->setBackground(Background::BACKDROP);
this->label = new Label(LabelStyle::NOTIFICATION, text, true);
label->setParent(this);
}
Notification::~Notification()
{
delete this->label;
}
void Notification::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
this->label->frame(ctx);
}
void Notification::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
unsigned padding = style->Notification.padding;
unsigned fontSize = style->Label.notificationFontSize;
float lineHeight = style->Label.notificationLineHeight;
// Layout the label
this->label->setWidth(this->getWidth() - padding * 2);
this->label->setHeight(0); // height is dynamic
this->label->invalidate(true); // layout directly to update height
unsigned minLabelHeight = (unsigned int)(lineHeight * fontSize) + fontSize; // 2 lines
unsigned labelYAdvance = padding;
if (this->label->getHeight() < minLabelHeight)
{
labelYAdvance += (minLabelHeight - this->label->getHeight()) / 2;
}
this->label->setBoundaries(
this->getX() + padding,
this->getY() + labelYAdvance,
this->label->getWidth(),
this->label->getHeight());
// Update our own height
this->setHeight(std::max(
this->label->getHeight() + padding * 2,
minLabelHeight + padding * 2));
}
}; // namespace brls

View File

@@ -0,0 +1,163 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/logger.hpp>
#include <borealis/popup_frame.hpp>
namespace brls
{
PopupFrame::PopupFrame(std::string title, unsigned char* imageBuffer, size_t imageBufferSize, AppletFrame* contentView, std::string subTitleLeft, std::string subTitleRight)
: contentView(contentView)
{
if (this->contentView)
{
this->contentView->setParent(this);
this->contentView->setHeaderStyle(HeaderStyle::POPUP);
this->contentView->setTitle(title);
this->contentView->setSubtitle(subTitleLeft, subTitleRight);
this->contentView->setIcon(imageBuffer, imageBufferSize);
this->contentView->invalidate();
}
contentView->setAnimateHint(true);
this->registerAction("Back", Key::B, [this] { return this->onCancel(); });
}
PopupFrame::PopupFrame(std::string title, std::string imagePath, AppletFrame* contentView, std::string subTitleLeft, std::string subTitleRight)
: contentView(contentView)
{
if (this->contentView)
{
this->contentView->setParent(this);
this->contentView->setHeaderStyle(HeaderStyle::POPUP);
this->contentView->setTitle(title);
this->contentView->setSubtitle(subTitleLeft, subTitleRight);
this->contentView->setIcon(imagePath);
this->contentView->invalidate();
}
contentView->setAnimateHint(true);
this->registerAction("Back", Key::B, [this] { return this->onCancel(); });
}
PopupFrame::PopupFrame(std::string title, AppletFrame* contentView, std::string subTitleLeft, std::string subTitleRight)
: contentView(contentView)
{
if (this->contentView)
{
this->contentView->setParent(this);
this->contentView->setHeaderStyle(HeaderStyle::POPUP);
this->contentView->setTitle(title);
this->contentView->setSubtitle(subTitleLeft, subTitleRight);
this->contentView->invalidate();
}
contentView->setAnimateHint(true);
this->registerAction("Back", Key::B, [this] { return this->onCancel(); });
}
void PopupFrame::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
// Backdrop
nvgFillColor(vg, a(ctx->theme->dropdownBackgroundColor));
nvgBeginPath(vg);
nvgRect(vg, 0, y, width, height);
nvgFill(vg);
// Background
nvgFillColor(vg, a(ctx->theme->backgroundColorRGB));
nvgBeginPath(vg);
nvgRect(vg, style->PopupFrame.edgePadding, y, width - style->PopupFrame.edgePadding * 2, height);
nvgFill(vg);
// TODO: Shadow
// Content view
nvgSave(vg);
nvgScissor(vg, style->PopupFrame.edgePadding, 0, style->PopupFrame.contentWidth, height);
this->contentView->frame(ctx);
nvgRestore(vg);
}
bool PopupFrame::onCancel()
{
Application::popView();
return true;
}
unsigned PopupFrame::getShowAnimationDuration(ViewAnimation animation)
{
return View::getShowAnimationDuration(animation) / 2;
}
void PopupFrame::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
this->contentView->setBoundaries(style->PopupFrame.edgePadding, 0, style->PopupFrame.contentWidth, this->getHeight());
this->contentView->invalidate();
}
View* PopupFrame::getDefaultFocus()
{
if (this->contentView)
return this->contentView->getDefaultFocus();
return nullptr;
}
void PopupFrame::open(std::string title, unsigned char* imageBuffer, size_t imageBufferSize, AppletFrame* contentView, std::string subTitleLeft, std::string subTitleRight)
{
PopupFrame* popupFrame = new PopupFrame(title, imageBuffer, imageBufferSize, contentView, subTitleLeft, subTitleRight);
Application::pushView(popupFrame);
}
void PopupFrame::open(std::string title, std::string imagePath, AppletFrame* contentView, std::string subTitleLeft, std::string subTitleRight)
{
PopupFrame* popupFrame = new PopupFrame(title, imagePath, contentView, subTitleLeft, subTitleRight);
Application::pushView(popupFrame);
}
void PopupFrame::open(std::string title, AppletFrame* contentView, std::string subTitleLeft, std::string subTitleRight)
{
PopupFrame* popupFrame = new PopupFrame(title, contentView, subTitleLeft, subTitleRight);
Application::pushView(popupFrame);
}
void PopupFrame::willAppear(bool resetState)
{
this->contentView->willAppear(resetState);
}
void PopupFrame::willDisappear(bool resetState)
{
this->contentView->willDisappear(resetState);
}
PopupFrame::~PopupFrame()
{
if (this->contentView)
delete this->contentView;
}
} // namespace brls

View File

@@ -0,0 +1,134 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 Billy Laws
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/progress_display.hpp>
namespace brls
{
ProgressDisplay::ProgressDisplay(ProgressDisplayFlags progressFlags)
{
if (progressFlags & ProgressDisplayFlags::PERCENTAGE)
this->label = new Label(LabelStyle::DIALOG, "0%", false);
if (progressFlags & ProgressDisplayFlags::SPINNER)
this->spinner = new ProgressSpinner();
}
void ProgressDisplay::setProgress(int current, int max)
{
if (current > max)
return;
this->progressPercentage = ((current * 100) / max);
if (!this->label)
return;
std::string labelText = std::to_string((unsigned)this->progressPercentage);
labelText += "%";
this->label->setText(labelText);
}
void ProgressDisplay::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
if (this->label)
{
this->label->setWidth(style->ProgressDisplay.percentageLabelWidth);
this->label->invalidate(true);
this->label->setBoundaries(
this->x + this->width - this->label->getWidth() / 2,
this->y + this->height / 2 - this->label->getHeight() / 2,
this->label->getWidth(),
this->label->getHeight());
}
if (this->spinner)
{
this->spinner->setWidth(this->height);
this->spinner->setHeight(this->height);
this->spinner->setBoundaries(
this->x,
this->y,
this->spinner->getWidth(),
this->spinner->getHeight());
}
}
void ProgressDisplay::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
unsigned progressBarWidth = width;
unsigned progressBarX = x;
if (this->label)
{
progressBarWidth -= this->label->getWidth();
this->label->frame(ctx);
}
if (this->spinner)
{
// 1.25 accounts for the extra width of the curve on sides of progress bar
progressBarWidth -= this->spinner->getWidth() * 1.25f;
progressBarX += this->spinner->getWidth() * 1.25f;
this->spinner->frame(ctx);
}
nvgBeginPath(vg);
nvgMoveTo(vg, progressBarX, y + height / 2);
nvgLineTo(vg, progressBarX + progressBarWidth, y + height / 2);
nvgStrokeColor(vg, a(ctx->theme->listItemSeparatorColor));
nvgStrokeWidth(vg, height / 3);
nvgLineCap(vg, NVG_ROUND);
nvgStroke(vg);
if (this->progressPercentage > 0.0f)
{
nvgBeginPath(vg);
nvgMoveTo(vg, progressBarX, y + height / 2);
nvgLineTo(vg, progressBarX + ((float)progressBarWidth * this->progressPercentage) / 100, y + height / 2);
nvgStrokeColor(vg, a(ctx->theme->listItemValueColor));
nvgStrokeWidth(vg, height / 3);
nvgLineCap(vg, NVG_ROUND);
nvgStroke(vg);
}
}
void ProgressDisplay::willAppear(bool resetState)
{
if (this->spinner)
this->spinner->willAppear(resetState);
}
void ProgressDisplay::willDisappear(bool resetState)
{
if (this->spinner)
this->spinner->willDisappear(resetState);
}
ProgressDisplay::~ProgressDisplay()
{
if (this->spinner)
delete this->spinner;
if (this->label)
delete this->label;
}
} // namespace brls

View File

@@ -0,0 +1,96 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 Billy Laws
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/progress_spinner.hpp>
namespace brls
{
ProgressSpinner::ProgressSpinner() {}
void ProgressSpinner::restartAnimation()
{
Style* style = Application::getStyle();
menu_animation_ctx_tag tag = (uintptr_t) & this->animationValue;
menu_animation_ctx_entry_t entry;
this->animationValue = 0.0f;
menu_animation_kill_by_tag(&tag);
entry.cb = [this](void* userdata) { this->restartAnimation(); };
entry.duration = style->AnimationDuration.progress;
entry.easing_enum = EASING_LINEAR;
entry.subject = &this->animationValue;
entry.tag = tag;
entry.target_value = 8.0f;
entry.tick = [](void* userdata) {};
entry.userdata = nullptr;
menu_animation_push(&entry);
}
void ProgressSpinner::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
this->height = std::min(this->width, this->height);
this->width = this->height;
}
void ProgressSpinner::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
NVGcolor barColor = a(ctx->theme->spinnerBarColor);
// Each bar of the spinner
for (int i = 0 + animationValue; i < 8 + animationValue; i++)
{
barColor.a = fmax((i - animationValue) / 8.0f, a(ctx->theme->spinnerBarColor).a);
nvgSave(vg);
nvgTranslate(vg, x + width / 2, y + height / 2);
nvgRotate(vg, nvgDegToRad(i * 45)); // Internal angle of octagon
nvgBeginPath(vg);
nvgMoveTo(vg, height * style->ProgressSpinner.centerGapMultiplier, 0);
nvgLineTo(vg, height / 2 - height * style->ProgressSpinner.centerGapMultiplier, 0);
nvgStrokeColor(vg, barColor);
nvgStrokeWidth(vg, height * style->ProgressSpinner.barWidthMultiplier);
nvgLineCap(vg, NVG_SQUARE);
nvgStroke(vg);
nvgRestore(vg);
}
}
void ProgressSpinner::willAppear(bool resetState)
{
this->restartAnimation();
}
void ProgressSpinner::willDisappear(bool resetState)
{
menu_animation_ctx_tag tag = (uintptr_t) & this->animationValue;
this->animationValue = 0.0f;
menu_animation_kill_by_tag(&tag);
}
} // namespace brls

View File

@@ -0,0 +1,55 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/logger.hpp>
#include <borealis/rectangle.hpp>
namespace brls
{
Rectangle::Rectangle(NVGcolor color)
{
this->setColor(color);
}
void Rectangle::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
NVGcolor color = a(this->color);
if (color.a == 0.0f)
return;
nvgFillColor(vg, color);
nvgBeginPath(vg);
nvgRect(vg, x, y, width, height);
nvgFill(vg);
}
void Rectangle::setColor(NVGcolor color)
{
this->color = color;
}
void Rectangle::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Nothing to do
}
} // namespace brls

View File

@@ -0,0 +1,88 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/repeating_task.hpp>
namespace brls
{
RepeatingTask::RepeatingTask(retro_time_t interval)
: interval(interval)
{
Application::getTaskManager()->registerRepeatingTask(this);
}
void RepeatingTask::run(retro_time_t currentTime)
{
this->lastRun = currentTime;
}
void RepeatingTask::start()
{
this->onStart();
this->running = true;
}
void RepeatingTask::pause()
{
this->running = false;
}
void RepeatingTask::stop()
{
this->pause();
this->stopRequested = true;
}
void RepeatingTask::fireNow()
{
if (!this->isRunning())
return;
retro_time_t currentTime = cpu_features_get_time_usec() / 1000;
this->run(currentTime);
}
retro_time_t RepeatingTask::getInterval()
{
return this->interval;
}
retro_time_t RepeatingTask::getLastRun()
{
return this->lastRun;
}
bool RepeatingTask::isRunning()
{
return this->running;
}
bool RepeatingTask::isStopRequested()
{
return this->stopRequested;
}
RepeatingTask::~RepeatingTask()
{
// Nothing to do here, only the Task Manager is supposed to free tasks
}
} // namespace brls

View File

@@ -0,0 +1,236 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2020 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <borealis/application.hpp>
#include <borealis/scroll_view.hpp>
namespace brls
{
void ScrollView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
if (!this->contentView)
return;
// Update scrolling if needed - try until it works
if (this->updateScrollingOnNextFrame && this->updateScrolling(false))
this->updateScrollingOnNextFrame = false;
// Enable scissoring
nvgSave(vg);
nvgScissor(vg, x, y, this->width, this->height);
// Draw content view
this->contentView->frame(ctx);
//Disable scissoring
nvgRestore(vg);
}
unsigned ScrollView::getYCenter(View* view)
{
return view->getY() + view->getHeight() / 2;
}
void ScrollView::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
this->prebakeScrolling();
// Update scrolling if needed
if (this->updateScrollingOnNextLayout)
{
this->updateScrollingOnNextLayout = false;
this->updateScrolling(false);
}
// Layout content view
if (this->contentView)
{
unsigned contentHeight = this->contentView->getHeight();
this->contentView->setBoundaries(
this->getX(),
this->getY() - roundf(this->scrollY * (float)contentHeight),
this->getWidth(),
contentHeight);
this->contentView->invalidate();
}
this->ready = true;
}
void ScrollView::willAppear(bool resetState)
{
this->prebakeScrolling();
// First scroll all the way to the top
// then wait for the first frame to scroll
// to the selected view if needed (only known then)
if (resetState)
{
this->startScrolling(false, 0.0f);
this->updateScrollingOnNextFrame = true; // focus may have changed since
}
if (this->contentView)
this->contentView->willAppear(resetState);
}
void ScrollView::willDisappear(bool resetState)
{
// Send event to content view
if (this->contentView)
this->contentView->willDisappear(resetState);
}
View* ScrollView::getDefaultFocus()
{
return this->contentView;
}
void ScrollView::setContentView(View* view)
{
this->contentView = view;
if (this->contentView)
{
this->contentView->setParent(this);
this->contentView->willAppear(true);
}
this->invalidate();
}
View* ScrollView::getContentView()
{
return this->contentView;
}
void ScrollView::prebakeScrolling()
{
// Prebaked values for scrolling
this->middleY = this->y + this->height / 2;
this->bottomY = this->y + this->height;
}
bool ScrollView::updateScrolling(bool animated)
{
// Don't scroll if layout hasn't been called yet
if (!this->ready || !this->contentView)
return false;
float contentHeight = (float)this->contentView->getHeight();
// Ensure content is laid out too
if (contentHeight == 0)
return false;
View* focusedView = Application::getCurrentFocus();
int currentSelectionMiddleOnScreen = focusedView->getY() + focusedView->getHeight() / 2;
float newScroll = -(this->scrollY * contentHeight) - ((float)currentSelectionMiddleOnScreen - (float)this->middleY);
// Bottom boundary
if ((float)this->y + newScroll + contentHeight < (float)this->bottomY)
newScroll = (float)this->height - contentHeight;
// Top boundary
if (newScroll > 0.0f)
newScroll = 0.0f;
// Apply 0.0f -> 1.0f scale
newScroll = abs(newScroll) / contentHeight;
//Start animation
this->startScrolling(animated, newScroll);
return true;
}
void ScrollView::startScrolling(bool animated, float newScroll)
{
if (newScroll == this->scrollY)
return;
menu_animation_ctx_tag tag = (uintptr_t) & this->scrollY;
menu_animation_kill_by_tag(&tag);
if (animated)
{
Style* style = Application::getStyle();
menu_animation_ctx_entry_t entry;
entry.cb = [](void* userdata) {};
entry.duration = style->AnimationDuration.highlight;
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->scrollY;
entry.tag = tag;
entry.target_value = newScroll;
entry.tick = [this](void* userdata) { this->scrollAnimationTick(); };
entry.userdata = nullptr;
menu_animation_push(&entry);
}
else
{
this->scrollY = newScroll;
}
this->invalidate(!animated); // layout immediately if not animated
}
void ScrollView::scrollAnimationTick()
{
this->invalidate();
}
void ScrollView::onChildFocusGained(View* child)
{
// layout hasn't been called yet, don't scroll
if (!this->ready)
return;
// Safety check to ensure that we don't have
// two children (setContentView called twice)
if (child != this->contentView)
return;
// Start scrolling
this->updateScrolling(true);
View::onChildFocusGained(child);
}
void ScrollView::onWindowSizeChanged()
{
this->updateScrollingOnNextLayout = true;
if (this->contentView)
this->contentView->onWindowSizeChanged();
}
ScrollView::~ScrollView()
{
if (this->contentView)
{
this->contentView->willDisappear(true);
delete this->contentView;
}
}
} // namespace brls

View File

@@ -0,0 +1,172 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/sidebar.hpp>
namespace brls
{
Sidebar::Sidebar()
: BoxLayout(BoxLayoutOrientation::VERTICAL)
{
Style* style = Application::getStyle();
this->setWidth(style->Sidebar.width);
this->setSpacing(style->Sidebar.spacing);
this->setMargins(style->Sidebar.marginTop, style->Sidebar.marginRight, style->Sidebar.marginBottom, style->Sidebar.marginLeft);
this->setBackground(Background::SIDEBAR);
}
View* Sidebar::getDefaultFocus()
{
// Sanity check
if (this->lastFocus >= this->children.size())
this->lastFocus = 0;
// Try to focus last focused one
View* toFocus = this->children[this->lastFocus]->view->getDefaultFocus();
if (toFocus)
return toFocus;
// Otherwise just get the first available item
return BoxLayout::getDefaultFocus();
}
void Sidebar::onChildFocusGained(View* child)
{
size_t position = *((size_t*)child->getParentUserData());
this->lastFocus = position;
BoxLayout::onChildFocusGained(child);
}
SidebarItem* Sidebar::addItem(std::string label, View* view)
{
SidebarItem* item = new SidebarItem(label, this);
item->setAssociatedView(view);
if (this->isEmpty())
setActive(item);
this->addView(item);
return item;
}
void Sidebar::addSeparator()
{
SidebarSeparator* separator = new SidebarSeparator();
this->addView(separator);
}
void Sidebar::setActive(SidebarItem* active)
{
if (currentActive)
currentActive->setActive(false);
currentActive = active;
active->setActive(true);
}
SidebarItem::SidebarItem(std::string label, Sidebar* sidebar)
: label(label)
, sidebar(sidebar)
{
Style* style = Application::getStyle();
this->setHeight(style->Sidebar.Item.height);
this->registerAction("OK", Key::A, [this] { return this->onClick(); });
}
void SidebarItem::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
// Label
nvgFillColor(vg, a(this->active ? ctx->theme->activeTabColor : ctx->theme->textColor));
nvgFontSize(vg, style->Sidebar.Item.textSize);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgBeginPath(vg);
nvgText(vg, x + style->Sidebar.Item.textOffsetX + style->Sidebar.Item.padding, y + height / 2, this->label.c_str(), nullptr);
// Active marker
if (this->active)
{
nvgFillColor(vg, a(ctx->theme->activeTabColor));
nvgBeginPath(vg);
nvgRect(vg, x + style->Sidebar.Item.padding, y + style->Sidebar.Item.padding, style->Sidebar.Item.activeMarkerWidth, style->Sidebar.Item.height - style->Sidebar.Item.padding * 2);
nvgFill(vg);
}
}
bool SidebarItem::onClick()
{
Application::onGamepadButtonPressed(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, false);
return true;
}
void SidebarItem::setActive(bool active)
{
this->active = active;
}
SidebarSeparator::SidebarSeparator()
{
Style* style = Application::getStyle();
this->setHeight(style->Sidebar.Separator.height);
}
void SidebarSeparator::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
nvgFillColor(vg, a(ctx->theme->sidebarSeparatorColor));
nvgBeginPath(vg);
nvgRect(vg, x, y + height / 2, width, 1);
nvgFill(vg);
}
void SidebarItem::setAssociatedView(View* view)
{
this->associatedView = view;
}
bool SidebarItem::isActive()
{
return this->active;
}
void SidebarItem::onFocusGained()
{
this->sidebar->setActive(this);
View::onFocusGained();
}
View* SidebarItem::getAssociatedView()
{
return this->associatedView;
}
SidebarItem::~SidebarItem()
{
if (this->associatedView)
delete this->associatedView;
}
} // namespace brls

View File

@@ -0,0 +1,136 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 Billy Laws
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/logger.hpp>
#include <borealis/staged_applet_frame.hpp>
#include <borealis/style.hpp>
#include <borealis/theme.hpp>
namespace brls
{
StagedAppletFrame::StagedAppletFrame()
: AppletFrame(true, true)
{
}
void StagedAppletFrame::addStage(View* view)
{
stageViews.push_back(view);
if (!currentStage)
enterStage(0, false);
}
void StagedAppletFrame::nextStage()
{
enterStage((int)currentStage + 1, true);
}
void StagedAppletFrame::previousStage()
{
enterStage((int)currentStage - 1, true);
}
// TODO: Add animation to bullets when changing stage
// TODO: Add left/right and right/left animations for views when changing stage (use show/hide with the animation)
void StagedAppletFrame::enterStage(int index, bool requestFocus)
{
if ((size_t)index >= stageViews.size())
return;
if (this->hasContentView())
stageViews[currentStage]->willDisappear(true);
currentStage = (size_t)index;
this->setContentView(stageViews[currentStage]); // calls willAppear
if (requestFocus)
Application::giveFocus(this->getDefaultFocus());
}
void StagedAppletFrame::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
AppletFrame::draw(vg, x, y, width, height, style, ctx);
if (stageViews.empty())
return;
for (size_t i = 0; i < stageViews.size(); i++)
{
bool current = (i == (stageViews.size() - 1) - currentStage);
// Border color for next stages
NVGcolor borderColor = ctx->theme->nextStageBulletColor;
nvgBeginPath(vg);
nvgCircle(vg, x + width - style->AppletFrame.separatorSpacing * (style->StagedAppletFrame.progressIndicatorSpacing + i) + style->AppletFrame.separatorSpacing + style->AppletFrame.separatorSpacing / 2,
y + style->AppletFrame.headerHeightRegular / 2, current ? style->StagedAppletFrame.progressIndicatorRadiusSelected : style->StagedAppletFrame.progressIndicatorRadiusUnselected);
// Current stage
if (current)
{
nvgFillColor(vg, a(ctx->theme->listItemValueColor));
nvgFill(vg);
}
// Previous stages
else if (i > (stageViews.size() - 1) - currentStage)
{
borderColor = ctx->theme->separatorColor;
nvgFillColor(vg, a(ctx->theme->textColor));
nvgFill(vg);
}
if (!current)
{
nvgStrokeColor(vg, a(borderColor));
nvgStrokeWidth(vg, style->StagedAppletFrame.progressIndicatorBorderWidth);
nvgStroke(vg);
}
}
}
unsigned StagedAppletFrame::getStagesCount()
{
return this->stageViews.size();
}
unsigned StagedAppletFrame::getCurrentStage()
{
return this->currentStage;
}
unsigned StagedAppletFrame::isLastStage()
{
return this->currentStage == this->stageViews.size() - 1;
}
StagedAppletFrame::~StagedAppletFrame()
{
for (View* view : this->stageViews)
delete view;
this->stageViews.clear();
this->setContentView(nullptr); // so that ~AppletFrame() doesn't free it twice
}
} // namespace brls

View File

@@ -0,0 +1,270 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/style.hpp>
namespace brls
{
Style Style::horizon()
{
Style style = Style();
style.AppletFrame = {
.headerHeightRegular = 88,
.headerHeightPopup = 129,
.footerHeight = 73,
.imageLeftPadding = 64,
.imageTopPadding = 20,
.imageSize = 52,
.separatorSpacing = 30,
.titleSize = 28,
.titleStart = 130,
.titleOffset = 5,
.footerTextSize = 22,
.footerTextSpacing = 30,
.slideAnimation = 20
};
style.Highlight = {
.strokeWidth = 5,
.cornerRadius = 0.5f,
.shadowWidth = 2,
.shadowOffset = 10,
.shadowFeather = 10,
.shadowOpacity = 128
};
style.Background = {
.sidebarBorderHeight = 16
};
style.Sidebar = {
.width = 410,
.spacing = 0,
.marginLeft = 88,
.marginRight = 30,
.marginTop = 40,
.marginBottom = 40,
.Item = {
.height = 70,
.textSize = 22,
.padding = 9,
.textOffsetX = 14,
.activeMarkerWidth = 4,
},
.Separator = { .height = 28 }
};
style.List = {
.marginLeftRight = 60,
.marginTopBottom = 42,
.spacing = 61,
.Item = {
.height = 69, // offset by 1 to have the highlight hide the separator
.heightWithSubLabel = 99,
.valueSize = 20,
.padding = 15,
.thumbnailPadding = 11,
.descriptionIndent = 20,
.descriptionSpacing = 16,
.indent = 40,
.selectRadius = 15 }
};
style.Label = {
.regularFontSize = 20,
.mediumFontSize = 18,
.smallFontSize = 16,
.descriptionFontSize = 16,
.crashFontSize = 24,
.buttonFontSize = 24,
.listItemFontSize = 24,
.notificationFontSize = 18,
.dialogFontSize = 24,
.hintFontSize = 22,
.lineHeight = 1.65f,
.notificationLineHeight = 1.35f
};
style.CrashFrame = {
.labelWidth = 0.60f,
.boxStrokeWidth = 5,
.boxSize = 64,
.boxSpacing = 90,
.buttonWidth = 356,
.buttonHeight = 60,
.buttonSpacing = 47
};
style.Button = {
.cornerRadius = 5.0f,
.highlightInset = 2,
.shadowWidth = 2.0f,
.shadowFeather = 10.0f,
.shadowOpacity = 63.75f,
.shadowOffset = 10.0f
};
style.TableRow = {
.headerHeight = 60,
.headerTextSize = 22,
.bodyHeight = 38,
.bodyIndent = 40,
.bodyTextSize = 18,
.padding = 15
};
style.Dropdown = {
.listWidth = 720,
.listPadding = 40,
.listItemHeight = 60,
.listItemTextSize = 20,
.headerHeight = 71,
.headerFontSize = 24,
.headerPadding = 70
};
style.PopupFrame = {
.edgePadding = 120,
.separatorSpacing = 30,
.footerHeight = 73,
.imageLeftPadding = 60,
.imageTopPadding = 17,
.imageSize = 100,
.contentWidth = 1040,
.contentHeight = 518,
.headerTextLeftPadding = 180,
.headerTextTopPadding = 64,
.subTitleLeftPadding = 182,
.subTitleTopPadding = 95,
.subTitleSpacing = 20,
.subTitleSeparatorLeftPadding = 280,
.subTitleSeparatorTopPadding = 92,
.subTitleSeparatorHeight = 20,
.headerFontSize = 28,
.subTitleFontSize = 16
};
style.StagedAppletFrame = {
.progressIndicatorSpacing = 4,
.progressIndicatorRadiusUnselected = 5 - 1, // minus half of border width
.progressIndicatorRadiusSelected = 8,
.progressIndicatorBorderWidth = 2
};
style.ProgressSpinner = {
.centerGapMultiplier = 0.2f,
.barWidthMultiplier = 0.06f
};
style.ProgressDisplay = {
.percentageLabelWidth = 70
};
style.Header = {
.height = 44,
.padding = 11,
.rectangleWidth = 5,
.fontSize = 18
};
style.FramerateCounter = {
.width = 125,
.height = 26
};
style.ThumbnailSidebar = {
.marginLeftRight = 109, // used for the image only = (410 - 192) / 2, image size is 192*192 with a 410px wide sidebar
.marginTopBottom = 47,
.buttonHeight = 70,
.buttonMargin = 60
};
style.AnimationDuration = {
.show = 250,
.showSlide = 125,
.highlight = 100,
.shake = 15,
.collapse = 100,
.progress = 1000,
.notificationTimeout = 4000
};
style.Notification = {
.width = 280,
.padding = 16,
.slideAnimation = 40
};
style.Dialog = {
.width = 770,
.height = 220,
.paddingTopBottom = 65,
.paddingLeftRight = 115,
.cornerRadius = 5.0f,
.buttonHeight = 72,
.buttonSeparatorHeight = 2,
.shadowWidth = 2.0f,
.shadowFeather = 10.0f,
.shadowOpacity = 63.75f,
.shadowOffset = 10.0f
};
return style;
}
} // namespace brls

View File

@@ -0,0 +1,43 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <unistd.h>
static int nxlink_sock = -1;
void userAppInit()
{
romfsInit();
socketInitializeDefault();
nxlink_sock = nxlinkStdio();
plInitialize(PlServiceType_User);
setsysInitialize();
}
void userAppExit()
{
if (nxlink_sock != -1)
close(nxlink_sock);
socketExit();
romfsExit();
plExit();
setsysExit();
}

View File

@@ -0,0 +1,126 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/logger.hpp>
#include <borealis/swkbd.hpp>
#include <cstring>
#include <iostream>
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace brls
{
#ifdef __SWITCH__
static SwkbdConfig createSwkbdBaseConfig(std::string headerText, std::string subText, int maxStringLength, std::string initialText)
{
SwkbdConfig config;
swkbdCreate(&config, 0);
swkbdConfigMakePresetDefault(&config);
swkbdConfigSetHeaderText(&config, headerText.c_str());
swkbdConfigSetSubText(&config, subText.c_str());
swkbdConfigSetStringLenMax(&config, maxStringLength);
swkbdConfigSetInitialText(&config, initialText.c_str());
swkbdConfigSetStringLenMin(&config, 1);
swkbdConfigSetBlurBackground(&config, true);
return config;
}
#else
static std::string terminalInput(std::string text)
{
printf("\033[0;94m[INPUT] \033[0;36m%s\033[0m: ", text.c_str());
std::string line;
std::getline(std::cin, line);
return line;
}
#endif
bool Swkbd::openForText(std::function<void(std::string)> f, std::string headerText, std::string subText, int maxStringLength, std::string initialText)
{
#ifdef __SWITCH__
SwkbdConfig config = createSwkbdBaseConfig(headerText, subText, maxStringLength, initialText);
swkbdConfigSetType(&config, SwkbdType_Normal);
swkbdConfigSetKeySetDisableBitmask(&config, SwkbdKeyDisableBitmask_At | SwkbdKeyDisableBitmask_Percent | SwkbdKeyDisableBitmask_ForwardSlash | SwkbdKeyDisableBitmask_Backslash);
char buffer[0x100];
if (R_SUCCEEDED(swkbdShow(&config, buffer, 0x100)) && strcmp(buffer, "") != 0)
{
f(buffer);
swkbdClose(&config);
return true;
}
swkbdClose(&config);
return false;
#else
std::string line = terminalInput(headerText);
f(line);
return true;
#endif
}
bool Swkbd::openForNumber(std::function<void(int)> f, std::string headerText, std::string subText, int maxStringLength, std::string initialText, std::string leftButton, std::string rightButton)
{
#ifdef __SWITCH__
SwkbdConfig config = createSwkbdBaseConfig(headerText, subText, maxStringLength, initialText);
swkbdConfigSetType(&config, SwkbdType_NumPad);
swkbdConfigSetLeftOptionalSymbolKey(&config, leftButton.c_str());
swkbdConfigSetRightOptionalSymbolKey(&config, rightButton.c_str());
swkbdConfigSetKeySetDisableBitmask(&config, SwkbdKeyDisableBitmask_At | SwkbdKeyDisableBitmask_Percent | SwkbdKeyDisableBitmask_ForwardSlash | SwkbdKeyDisableBitmask_Backslash);
char buffer[0x100];
if (R_SUCCEEDED(swkbdShow(&config, buffer, 0x100)) && strcmp(buffer, "") != 0)
{
f(std::stol(buffer));
swkbdClose(&config);
return true;
}
swkbdClose(&config);
return false;
#else
std::string line = terminalInput(headerText);
try
{
f(stol(line));
return true;
}
catch (const std::exception& e)
{
Logger::error("Could not parse input, did you enter a valid integer?");
return false;
}
#endif
}
} // namespace brls

View File

@@ -0,0 +1,117 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 WerWolv
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/box_layout.hpp>
#include <borealis/rectangle.hpp>
#include <borealis/sidebar.hpp>
#include <borealis/tab_frame.hpp>
namespace brls
{
TabFrame::TabFrame()
: AppletFrame(false, true)
{
//Create sidebar
this->sidebar = new Sidebar();
// Setup content view
this->layout = new BoxLayout(BoxLayoutOrientation::HORIZONTAL);
layout->addView(sidebar);
this->setContentView(layout);
}
bool TabFrame::onCancel()
{
// Go back to sidebar if not already focused
if (!this->sidebar->isChildFocused())
{
Application::onGamepadButtonPressed(GLFW_GAMEPAD_BUTTON_DPAD_LEFT, false);
return true;
}
return AppletFrame::onCancel();
}
void TabFrame::switchToView(View* view)
{
if (this->rightPane == view)
return;
if (this->layout->getViewsCount() > 1)
{
if (this->rightPane)
this->rightPane->willDisappear(true);
this->layout->removeView(1, false);
}
if (view != nullptr)
{
this->layout->addView(view, true, true); // addView() calls willAppear()
this->rightPane = view;
}
}
void TabFrame::addTab(std::string label, View* view)
{
SidebarItem* item = this->sidebar->addItem(label, view);
item->getFocusEvent()->subscribe([this](View* view) {
if (SidebarItem* item = dynamic_cast<SidebarItem*>(view))
this->switchToView(item->getAssociatedView());
});
// Switch to first one as soon as we add it
if (!this->rightPane)
{
Logger::debug("Switching to the first tab");
this->switchToView(view);
}
}
void TabFrame::addSeparator()
{
this->sidebar->addSeparator();
}
View* TabFrame::getDefaultFocus()
{
// Try to focus the right pane
if (this->layout->getViewsCount() > 1)
{
View* newFocus = this->rightPane->getDefaultFocus();
if (newFocus)
return newFocus;
}
// Otherwise focus sidebar
return this->sidebar->getDefaultFocus();
}
TabFrame::~TabFrame()
{
switchToView(nullptr);
// Content view is freed by ~AppletFrame()
}
} // namespace brls

View File

@@ -0,0 +1,147 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/table.hpp>
namespace brls
{
TableRow* Table::addRow(TableRowType type, std::string label, std::string value)
{
TableRow* row = new TableRow(type, label, value);
this->rows.push_back(row);
return row;
}
void Table::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
ThemeValues* theme = ctx->theme;
unsigned yAdvance = 0;
for (size_t i = 0; i < this->rows.size(); i++)
{
bool even = i % 2 == 0;
TableRow* row = this->rows[i];
// Get appearance
unsigned indent = 0;
unsigned height = 0;
unsigned fontSize = 0;
NVGcolor backgroundColor;
NVGcolor textColor;
switch (row->getType())
{
case TableRowType::HEADER:
indent = 0;
height = style->TableRow.headerHeight;
textColor = theme->textColor;
fontSize = style->TableRow.headerTextSize;
break;
case TableRowType::BODY:
indent = style->TableRow.bodyIndent;
height = style->TableRow.bodyHeight;
textColor = theme->tableBodyTextColor;
fontSize = style->TableRow.bodyTextSize;
break;
}
backgroundColor = even ? theme->tableEvenBackgroundColor : transparent;
// Background
nvgFillColor(vg, a(backgroundColor));
nvgBeginPath(vg);
nvgRect(vg, x + indent, y + yAdvance, width - indent, height);
nvgFill(vg);
// Text
nvgFillColor(vg, a(textColor));
nvgFontFaceId(vg, ctx->fontStash->regular);
nvgFontSize(vg, fontSize);
// Label
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + indent + style->TableRow.padding, y + yAdvance + height / 2, row->getLabel()->c_str(), nullptr);
// Value
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
nvgBeginPath(vg);
nvgText(vg, x + width - style->TableRow.padding, y + yAdvance + height / 2, row->getValue()->c_str(), nullptr);
yAdvance += height;
}
}
void Table::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
unsigned height = 0;
for (TableRow* row : this->rows)
{
switch (row->getType())
{
case TableRowType::HEADER:
height += style->TableRow.headerHeight;
break;
case TableRowType::BODY:
height += style->TableRow.bodyHeight;
break;
}
}
this->setHeight(height);
}
Table::~Table()
{
for (TableRow* row : this->rows)
{
delete row;
}
}
TableRow::TableRow(TableRowType type, std::string label, std::string value)
: type(type)
, label(label)
, value(value)
{
}
std::string* TableRow::getLabel()
{
return &this->label;
}
std::string* TableRow::getValue()
{
return &this->value;
}
void TableRow::setValue(std::string value)
{
this->value = value;
}
TableRowType TableRow::getType()
{
return this->type;
}
} // namespace brls

View File

@@ -0,0 +1,69 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <features/features_cpu.h>
#include <borealis/task_manager.hpp>
namespace brls
{
void TaskManager::frame()
{
// Repeating tasks
retro_time_t currentTime = cpu_features_get_time_usec() / 1000;
for (auto i = this->repeatingTasks.begin(); i != this->repeatingTasks.end(); i++)
{
RepeatingTask* task = *i;
// Stop the task if needed
if (task->isStopRequested())
{
this->stopRepeatingTask(task);
this->repeatingTasks.erase(i--);
}
// Fire it
else if (task->isRunning() && currentTime - task->getLastRun() > task->getInterval())
{
task->run(currentTime);
}
}
}
void TaskManager::registerRepeatingTask(RepeatingTask* task)
{
this->repeatingTasks.push_back(task);
}
void TaskManager::stopRepeatingTask(RepeatingTask* task)
{
task->onStop();
delete task;
}
TaskManager::~TaskManager()
{
// Stop all repeating tasks
for (RepeatingTask* task : this->repeatingTasks)
this->stopRepeatingTask(task);
this->repeatingTasks.clear();
}
} // namespace brls

View File

@@ -0,0 +1,130 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/theme.hpp>
#define DARK theme.colors[ThemeVariant_DARK]
#define LIGHT theme.colors[ThemeVariant_LIGHT]
namespace brls
{
// Default colors
Theme Theme::horizon()
{
Theme theme = Theme();
// Light variant
LIGHT.backgroundColor[0] = 0.922f;
LIGHT.backgroundColor[1] = 0.922f;
LIGHT.backgroundColor[2] = 0.922f;
LIGHT.backgroundColorRGB = nvgRGB(235, 235, 235);
LIGHT.textColor = nvgRGB(51, 51, 51);
LIGHT.descriptionColor = nvgRGB(140, 140, 140);
LIGHT.notificationTextColor = nvgRGB(255, 255, 255);
LIGHT.backdropColor = nvgRGBA(0, 0, 0, 178);
LIGHT.separatorColor = nvgRGB(45, 45, 45);
LIGHT.sidebarColor = nvgRGB(240, 240, 240);
LIGHT.activeTabColor = nvgRGB(49, 79, 235);
LIGHT.sidebarSeparatorColor = nvgRGB(208, 208, 208);
LIGHT.highlightBackgroundColor = nvgRGB(252, 255, 248);
LIGHT.highlightColor1 = nvgRGB(13, 182, 213);
LIGHT.highlightColor2 = nvgRGB(80, 239, 217);
LIGHT.listItemSeparatorColor = nvgRGB(207, 207, 207);
LIGHT.listItemValueColor = nvgRGB(43, 81, 226);
LIGHT.listItemFaintValueColor = nvgRGB(181, 184, 191);
LIGHT.tableEvenBackgroundColor = nvgRGB(240, 240, 240);
LIGHT.tableBodyTextColor = nvgRGB(131, 131, 131);
LIGHT.dropdownBackgroundColor = nvgRGBA(0, 0, 0, 178);
LIGHT.nextStageBulletColor = nvgRGB(165, 165, 165);
LIGHT.spinnerBarColor = nvgRGBA(131, 131, 131, 102);
LIGHT.headerRectangleColor = nvgRGB(127, 127, 127);
LIGHT.buttonPlainEnabledBackgroundColor = nvgRGB(50, 79, 241);
LIGHT.buttonPlainDisabledBackgroundColor = nvgRGB(201, 201, 209);
LIGHT.buttonPlainEnabledTextColor = nvgRGB(255, 255, 255);
LIGHT.buttonPlainDisabledTextColor = nvgRGB(220, 220, 228);
LIGHT.dialogColor = nvgRGB(240, 240, 240);
LIGHT.dialogBackdrop = nvgRGBA(0, 0, 0, 100);
LIGHT.dialogButtonColor = nvgRGB(46, 78, 255);
LIGHT.dialogButtonSeparatorColor = nvgRGB(210, 210, 210);
// Dark variant
DARK.backgroundColor[0] = 0.176f;
DARK.backgroundColor[1] = 0.176f;
DARK.backgroundColor[2] = 0.176f;
DARK.backgroundColorRGB = nvgRGB(45, 45, 45);
DARK.textColor = nvgRGB(255, 255, 255);
DARK.descriptionColor = nvgRGB(163, 163, 163);
DARK.notificationTextColor = nvgRGB(255, 255, 255);
DARK.backdropColor = nvgRGBA(0, 0, 0, 178);
DARK.separatorColor = nvgRGB(255, 255, 255);
DARK.sidebarColor = nvgRGB(50, 50, 50);
DARK.activeTabColor = nvgRGB(0, 255, 204);
DARK.sidebarSeparatorColor = nvgRGB(81, 81, 81);
DARK.highlightBackgroundColor = nvgRGB(31, 34, 39);
DARK.highlightColor1 = nvgRGB(25, 138, 198);
DARK.highlightColor2 = nvgRGB(137, 241, 242);
DARK.listItemSeparatorColor = nvgRGB(78, 78, 78);
DARK.listItemValueColor = nvgRGB(88, 195, 169);
DARK.listItemFaintValueColor = nvgRGB(93, 103, 105);
DARK.tableEvenBackgroundColor = nvgRGB(57, 58, 60);
DARK.tableBodyTextColor = nvgRGB(155, 157, 156);
DARK.dropdownBackgroundColor = nvgRGBA(0, 0, 0, 178); // TODO: 178 may be too much for dark theme
DARK.nextStageBulletColor = nvgRGB(165, 165, 165);
DARK.spinnerBarColor = nvgRGBA(131, 131, 131, 102); // TODO: get this right
DARK.headerRectangleColor = nvgRGB(160, 160, 160);
DARK.buttonPlainEnabledBackgroundColor = nvgRGB(1, 255, 201);
DARK.buttonPlainDisabledBackgroundColor = nvgRGB(83, 87, 86);
DARK.buttonPlainEnabledTextColor = nvgRGB(52, 41, 55);
DARK.buttonPlainDisabledTextColor = nvgRGB(71, 75, 74);
DARK.dialogColor = nvgRGB(70, 70, 70);
DARK.dialogBackdrop = nvgRGBA(0, 0, 0, 100);
DARK.dialogButtonColor = nvgRGB(3, 251, 199);
DARK.dialogButtonSeparatorColor = nvgRGB(103, 103, 103);
return theme;
}
} // namespace brls

View File

@@ -0,0 +1,242 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019 natinusala
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <borealis/application.hpp>
#include <borealis/thumbnail_frame.hpp>
namespace brls
{
ThumbnailFrame::ThumbnailFrame()
: AppletFrame(true, false)
{
// Create the ThumbnailSidebar
this->sidebar = new ThumbnailSidebar();
// Setup content view
this->boxLayout = new BoxLayout(BoxLayoutOrientation::HORIZONTAL);
AppletFrame::setContentView(this->boxLayout);
}
void ThumbnailFrame::setContentView(View* view)
{
this->thumbnailContentView = view;
// Clear the layout
this->boxLayout->clear();
// Add the views
this->boxLayout->addView(view);
this->boxLayout->addView(this->sidebar);
// Invalidate
this->invalidate();
}
void ThumbnailFrame::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
// Resize content view
if (this->thumbnailContentView)
{
unsigned sidebarWidth = this->sidebar->getWidth();
this->thumbnailContentView->setWidth(this->getWidth() - sidebarWidth - this->leftPadding - this->rightPadding);
}
// Layout the rest
AppletFrame::layout(vg, style, stash);
}
ThumbnailSidebar* ThumbnailFrame::getSidebar()
{
return this->sidebar;
}
ThumbnailFrame::~ThumbnailFrame()
{
// If content view wasn't set, free the sidebar manually
if (!this->thumbnailContentView)
delete this->sidebar;
}
ThumbnailSidebar::ThumbnailSidebar()
{
Style* style = Application::getStyle();
this->setBackground(Background::SIDEBAR);
this->setWidth(style->Sidebar.width);
this->button = (new Button(ButtonStyle::PLAIN))->setLabel("Save");
this->button->setParent(this);
}
Button* ThumbnailSidebar::getButton()
{
return this->button;
}
void ThumbnailSidebar::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
if (this->image)
this->image->frame(ctx);
if (this->title)
this->title->frame(ctx);
if (this->subTitle)
this->subTitle->frame(ctx);
this->button->frame(ctx);
}
void ThumbnailSidebar::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
unsigned sidebarWidth = getWidth() - style->AppletFrame.separatorSpacing;
unsigned yAdvance = getY() + style->ThumbnailSidebar.marginTopBottom;
unsigned titleX = getX() + style->ThumbnailSidebar.marginLeftRight / 2;
unsigned titleWidth = sidebarWidth - style->ThumbnailSidebar.marginLeftRight;
// Image
if (this->image)
{
unsigned imageX = getX() + style->ThumbnailSidebar.marginLeftRight;
unsigned imageWidth = sidebarWidth - style->ThumbnailSidebar.marginLeftRight * 2;
this->image->setBoundaries(
imageX,
yAdvance,
imageWidth,
imageWidth);
yAdvance += this->image->getHeight() + style->ThumbnailSidebar.marginTopBottom;
}
// Title
if (this->title)
{
this->title->setBoundaries(
titleX,
yAdvance,
titleWidth,
0 // height is dynamic
);
// Call layout directly to update height
this->title->invalidate(true);
yAdvance += this->title->getHeight() + style->ThumbnailSidebar.marginTopBottom / 2;
}
// Subtitle
if (this->subTitle)
{
this->subTitle->setBoundaries(
titleX,
yAdvance,
titleWidth,
0 // height doesn't matter
);
this->subTitle->invalidate();
}
//Button
unsigned buttonWidth = sidebarWidth - style->ThumbnailSidebar.buttonMargin * 2;
unsigned buttonHeight = style->ThumbnailSidebar.buttonHeight;
this->button->setBoundaries(
getX() + style->ThumbnailSidebar.buttonMargin,
getY() + getHeight() - style->ThumbnailSidebar.marginTopBottom - buttonHeight,
buttonWidth,
buttonHeight);
}
View* ThumbnailSidebar::getDefaultFocus()
{
return this->button->getDefaultFocus();
}
void ThumbnailSidebar::setThumbnail(std::string imagePath)
{
if (this->image)
{
this->image->setImage(imagePath);
}
else
{
this->image = new Image(imagePath);
this->image->setParent(this);
this->invalidate();
}
}
void ThumbnailSidebar::setThumbnail(unsigned char* buffer, size_t bufferSize)
{
if (this->image)
{
this->image->setImage(buffer, bufferSize);
}
else
{
this->image = new Image(buffer, bufferSize);
this->image->setParent(this);
this->invalidate();
}
}
void ThumbnailSidebar::setSubtitle(std::string subTitle)
{
if (!this->subTitle)
{
this->subTitle = new Label(LabelStyle::DESCRIPTION, "");
this->subTitle->setParent(this);
}
this->subTitle->setText(subTitle);
this->invalidate();
}
// TODO: Add ellipsis if the title exceeds three lines
void ThumbnailSidebar::setTitle(std::string title)
{
if (!this->title)
{
this->title = new Label(LabelStyle::REGULAR, "", true);
this->title->setParent(this);
}
this->title->setText(title);
this->invalidate();
}
ThumbnailSidebar::~ThumbnailSidebar()
{
if (this->image)
delete this->image;
if (this->title)
delete this->title;
if (this->subTitle)
delete this->subTitle;
delete this->button;
}
} // namespace brls

View File

@@ -0,0 +1,630 @@
/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <algorithm>
#include <borealis/animations.hpp>
#include <borealis/application.hpp>
#include <borealis/view.hpp>
namespace brls
{
NVGcolor transparent = nvgRGBA(0, 0, 0, 0);
static int shakeAnimation(float t, float a) // a = amplitude
{
// Damped sine wave
float w = 0.8f; // period
float c = 0.35f; // damp factor
return roundf(a * exp(-(c * t)) * sin(w * t));
}
void View::shakeHighlight(FocusDirection direction)
{
this->highlightShaking = true;
this->highlightShakeStart = cpu_features_get_time_usec() / 1000;
this->highlightShakeDirection = direction;
this->highlightShakeAmplitude = std::rand() % 15 + 10;
}
float View::getAlpha(bool child)
{
return this->alpha * (this->parent ? this->parent->getAlpha(true) : 1.0f);
}
NVGcolor View::a(NVGcolor color)
{
NVGcolor newColor = color;
newColor.a *= this->getAlpha();
return newColor;
}
NVGpaint View::a(NVGpaint paint)
{
NVGpaint newPaint = paint;
newPaint.innerColor.a *= this->getAlpha();
newPaint.outerColor.a *= this->getAlpha();
return newPaint;
}
// TODO: Only draw views that are onscreen (w/ some margins)
void View::frame(FrameContext* ctx)
{
Style* style = Application::getStyle();
ThemeValues* oldTheme = ctx->theme;
nvgSave(ctx->vg);
// Theme override
if (this->themeOverride)
ctx->theme = themeOverride;
// Layout if needed
if (this->dirty)
{
this->invalidate(true);
this->dirty = false;
}
if (this->alpha > 0.0f && this->collapseState != 0.0f)
{
// Draw background
this->drawBackground(ctx->vg, ctx, style);
// Draw highlight background
if (this->highlightAlpha > 0.0f && this->isHighlightBackgroundEnabled())
this->drawHighlight(ctx->vg, ctx->theme, this->highlightAlpha, style, true);
// Collapse clipping
if (this->collapseState < 1.0f)
{
nvgSave(ctx->vg);
nvgIntersectScissor(ctx->vg, x, y, this->width, this->height * this->collapseState);
}
// Draw the view
this->draw(ctx->vg, this->x, this->y, this->width, this->height, style, ctx);
// Draw highlight
if (this->highlightAlpha > 0.0f)
this->drawHighlight(ctx->vg, ctx->theme, this->highlightAlpha, style, false);
//Reset clipping
if (this->collapseState < 1.0f)
nvgRestore(ctx->vg);
}
// Cleanup
if (this->themeOverride)
ctx->theme = oldTheme;
nvgRestore(ctx->vg);
}
void View::collapse(bool animated)
{
menu_animation_ctx_tag tag = (uintptr_t) & this->collapseState;
menu_animation_kill_by_tag(&tag);
if (animated)
{
Style* style = Application::getStyle();
menu_animation_ctx_entry_t entry;
entry.cb = [](void* userdata) {};
entry.duration = style->AnimationDuration.collapse;
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->collapseState;
entry.tag = tag;
entry.target_value = 0.0f;
entry.tick = [this](void* userdata) { if (this->hasParent()) this->getParent()->invalidate(); };
entry.userdata = nullptr;
menu_animation_push(&entry);
}
else
{
this->collapseState = 0.0f;
}
}
bool View::isCollapsed()
{
return this->collapseState < 1.0f;
}
void View::expand(bool animated)
{
menu_animation_ctx_tag tag = (uintptr_t) & this->collapseState;
menu_animation_kill_by_tag(&tag);
if (animated)
{
Style* style = Application::getStyle();
menu_animation_ctx_entry_t entry;
entry.cb = [](void* userdata) {};
entry.duration = style->AnimationDuration.collapse;
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->collapseState;
entry.tag = tag;
entry.target_value = 1.0f;
entry.tick = [this](void* userdata) { if (this->hasParent()) this->getParent()->invalidate(); };
entry.userdata = nullptr;
menu_animation_push(&entry);
}
else
{
this->collapseState = 1.0f;
}
}
// TODO: Slight glow all around
void View::drawHighlight(NVGcontext* vg, ThemeValues* theme, float alpha, Style* style, bool background)
{
nvgSave(vg);
nvgResetScissor(vg);
unsigned insetTop, insetRight, insetBottom, insetLeft;
this->getHighlightInsets(&insetTop, &insetRight, &insetBottom, &insetLeft);
float cornerRadius;
this->getHighlightMetrics(style, &cornerRadius);
unsigned x = this->x - insetLeft - style->Highlight.strokeWidth / 2;
unsigned y = this->y - insetTop - style->Highlight.strokeWidth / 2;
unsigned width = this->width + insetLeft + insetRight + style->Highlight.strokeWidth;
unsigned height = this->height + insetTop + insetBottom + style->Highlight.strokeWidth;
// Shake animation
if (this->highlightShaking)
{
retro_time_t curTime = cpu_features_get_time_usec() / 1000;
retro_time_t t = (curTime - highlightShakeStart) / 10;
if (t >= style->AnimationDuration.shake)
{
this->highlightShaking = false;
}
else
{
switch (this->highlightShakeDirection)
{
case FocusDirection::RIGHT:
x += shakeAnimation(t, this->highlightShakeAmplitude);
break;
case FocusDirection::LEFT:
x -= shakeAnimation(t, this->highlightShakeAmplitude);
break;
case FocusDirection::DOWN:
y += shakeAnimation(t, this->highlightShakeAmplitude);
break;
case FocusDirection::UP:
y -= shakeAnimation(t, this->highlightShakeAmplitude);
break;
default:
break;
}
}
}
// Draw
if (background)
{
// Background
nvgFillColor(vg, RGBAf(theme->highlightBackgroundColor.r, theme->highlightBackgroundColor.g, theme->highlightBackgroundColor.b, this->highlightAlpha));
nvgBeginPath(vg);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgFill(vg);
}
else
{
// Shadow
NVGpaint shadowPaint = nvgBoxGradient(vg,
x, y + style->Highlight.shadowWidth,
width, height,
cornerRadius * 2, style->Highlight.shadowFeather,
RGBA(0, 0, 0, style->Highlight.shadowOpacity * alpha), transparent);
nvgBeginPath(vg);
nvgRect(vg, x - style->Highlight.shadowOffset, y - style->Highlight.shadowOffset,
width + style->Highlight.shadowOffset * 2, height + style->Highlight.shadowOffset * 3);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgPathWinding(vg, NVG_HOLE);
nvgFillPaint(vg, shadowPaint);
nvgFill(vg);
// Border
float gradientX, gradientY, color;
menu_animation_get_highlight(&gradientX, &gradientY, &color);
NVGcolor pulsationColor = RGBAf((color * theme->highlightColor1.r) + (1 - color) * theme->highlightColor2.r,
(color * theme->highlightColor1.g) + (1 - color) * theme->highlightColor2.g,
(color * theme->highlightColor1.b) + (1 - color) * theme->highlightColor2.b,
alpha);
NVGcolor borderColor = theme->highlightColor2;
borderColor.a = 0.5f * alpha * this->getAlpha();
NVGpaint border1Paint = nvgRadialGradient(vg,
x + gradientX * width, y + gradientY * height,
style->Highlight.strokeWidth * 10, style->Highlight.strokeWidth * 40,
borderColor, transparent);
NVGpaint border2Paint = nvgRadialGradient(vg,
x + (1 - gradientX) * width, y + (1 - gradientY) * height,
style->Highlight.strokeWidth * 10, style->Highlight.strokeWidth * 40,
borderColor, transparent);
nvgBeginPath(vg);
nvgStrokeColor(vg, pulsationColor);
nvgStrokeWidth(vg, style->Highlight.strokeWidth);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgStroke(vg);
nvgBeginPath(vg);
nvgStrokePaint(vg, border1Paint);
nvgStrokeWidth(vg, style->Highlight.strokeWidth);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgStroke(vg);
nvgBeginPath(vg);
nvgStrokePaint(vg, border2Paint);
nvgStrokeWidth(vg, style->Highlight.strokeWidth);
nvgRoundedRect(vg, x, y, width, height, cornerRadius);
nvgStroke(vg);
}
nvgRestore(vg);
}
void View::setBackground(Background background)
{
this->background = background;
}
void View::drawBackground(NVGcontext* vg, FrameContext* ctx, Style* style)
{
switch (this->background)
{
case Background::SIDEBAR:
{
unsigned backdropHeight = style->Background.sidebarBorderHeight;
// Solid color
nvgBeginPath(vg);
nvgFillColor(vg, a(ctx->theme->sidebarColor));
nvgRect(vg, this->x, this->y + backdropHeight, this->width, this->height - backdropHeight * 2);
nvgFill(vg);
//Borders gradient
// Top
NVGpaint topGradient = nvgLinearGradient(vg, this->x, this->y + backdropHeight, this->x, this->y, a(ctx->theme->sidebarColor), transparent);
nvgBeginPath(vg);
nvgFillPaint(vg, topGradient);
nvgRect(vg, this->x, this->y, this->width, backdropHeight);
nvgFill(vg);
// Bottom
NVGpaint bottomGradient = nvgLinearGradient(vg, this->x, this->y + this->height - backdropHeight, this->x, this->y + this->height, a(ctx->theme->sidebarColor), transparent);
nvgBeginPath(vg);
nvgFillPaint(vg, bottomGradient);
nvgRect(vg, this->x, this->y + this->height - backdropHeight, this->width, backdropHeight);
nvgFill(vg);
break;
}
case Background::DEBUG:
{
nvgFillColor(vg, RGB(255, 0, 0));
nvgBeginPath(vg);
nvgRect(vg, this->x, this->y, this->width, this->height);
nvgFill(vg);
break;
}
case Background::BACKDROP:
{
nvgFillColor(vg, a(ctx->theme->backdropColor));
nvgBeginPath(vg);
nvgRect(vg, this->x, this->y, this->width, this->height);
nvgFill(vg);
}
case Background::NONE:
break;
}
}
void View::registerAction(std::string hintText, Key key, ActionListener actionListener, bool hidden)
{
if (auto it = std::find(this->actions.begin(), this->actions.end(), key); it != this->actions.end())
*it = { key, hintText, true, hidden, actionListener };
else
this->actions.push_back({ key, hintText, true, hidden, actionListener });
}
void View::updateActionHint(Key key, std::string hintText)
{
if (auto it = std::find(this->actions.begin(), this->actions.end(), key); it != this->actions.end())
it->hintText = hintText;
Application::getGlobalHintsUpdateEvent()->fire();
}
void View::setActionAvailable(Key key, bool available)
{
if (auto it = std::find(this->actions.begin(), this->actions.end(), key); it != this->actions.end())
it->available = available;
}
void View::setBoundaries(int x, int y, unsigned width, unsigned height)
{
this->x = x;
this->y = y;
this->width = width;
this->height = height;
}
void View::setParent(View* parent, void* parentUserdata)
{
this->parent = parent;
this->parentUserdata = parentUserdata;
}
void* View::getParentUserData()
{
return this->parentUserdata;
}
bool View::isFocused()
{
return this->focused;
}
View* View::getParent()
{
return this->parent;
}
bool View::hasParent()
{
return this->parent;
}
void View::setWidth(unsigned width)
{
this->width = width;
}
void View::setHeight(unsigned height)
{
this->height = height;
}
int View::getX()
{
return this->x;
}
int View::getY()
{
return this->y;
}
unsigned View::getHeight(bool includeCollapse)
{
return this->height * (includeCollapse ? this->collapseState : 1.0f);
}
unsigned View::getWidth()
{
return this->width;
}
void View::onFocusGained()
{
this->focused = true;
Style* style = Application::getStyle();
menu_animation_ctx_tag tag = (uintptr_t)this->highlightAlpha;
menu_animation_ctx_entry_t entry;
entry.cb = [](void* userdata) {};
entry.duration = style->AnimationDuration.highlight;
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->highlightAlpha;
entry.tag = tag;
entry.target_value = 1.0f;
entry.tick = [](void* userdata) {};
entry.userdata = nullptr;
menu_animation_push(&entry);
this->focusEvent.fire(this);
if (this->hasParent())
this->getParent()->onChildFocusGained(this);
}
GenericEvent* View::getFocusEvent()
{
return &this->focusEvent;
}
/**
* Fired when focus is lost
*/
void View::onFocusLost()
{
this->focused = false;
Style* style = Application::getStyle();
menu_animation_ctx_tag tag = (uintptr_t)this->highlightAlpha;
menu_animation_ctx_entry_t entry;
entry.cb = [](void* userdata) {};
entry.duration = style->AnimationDuration.highlight;
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->highlightAlpha;
entry.tag = tag;
entry.target_value = 0.0f;
entry.tick = [](void* userdata) {};
entry.userdata = nullptr;
menu_animation_push(&entry);
if (this->hasParent())
this->getParent()->onChildFocusLost(this);
}
void View::setForceTranslucent(bool translucent)
{
this->forceTranslucent = translucent;
}
unsigned View::getShowAnimationDuration(ViewAnimation animation)
{
Style* style = Application::getStyle();
if (animation == ViewAnimation::SLIDE_LEFT || animation == ViewAnimation::SLIDE_RIGHT)
return style->AnimationDuration.showSlide;
return style->AnimationDuration.show;
}
void View::show(std::function<void(void)> cb, bool animate, ViewAnimation animation)
{
brls::Logger::debug("Showing %s with animation %d", this->describe().c_str(), animation);
this->hidden = false;
menu_animation_ctx_tag tag = (uintptr_t) & this->alpha;
menu_animation_kill_by_tag(&tag);
this->fadeIn = true;
if (animate)
{
this->alpha = 0.0f;
menu_animation_ctx_entry_t entry;
entry.cb = [this, cb](void* userdata) {
this->fadeIn = false;
this->onShowAnimationEnd();
cb();
};
entry.duration = this->getShowAnimationDuration(animation);
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->alpha;
entry.tag = tag;
entry.target_value = 1.0f;
entry.tick = [](void* userdata) {};
entry.userdata = nullptr;
menu_animation_push(&entry);
}
else
{
this->alpha = 1.0f;
this->fadeIn = false;
this->onShowAnimationEnd();
cb();
}
}
void View::hide(std::function<void(void)> cb, bool animated, ViewAnimation animation)
{
brls::Logger::debug("Hiding %s with animation %d", this->describe().c_str(), animation);
this->hidden = true;
this->fadeIn = false;
menu_animation_ctx_tag tag = (uintptr_t) & this->alpha;
menu_animation_kill_by_tag(&tag);
if (animated)
{
this->alpha = 1.0f;
menu_animation_ctx_entry_t entry;
entry.cb = [cb](void* userdata) { cb(); };
entry.duration = this->getShowAnimationDuration(animation);
entry.easing_enum = EASING_OUT_QUAD;
entry.subject = &this->alpha;
entry.tag = tag;
entry.target_value = 0.0f;
entry.tick = [](void* userdata) {};
entry.userdata = nullptr;
menu_animation_push(&entry);
}
else
{
this->alpha = 0.0f;
cb();
}
}
bool View::isHidden()
{
return this->hidden;
}
void View::overrideThemeVariant(ThemeValues* theme)
{
this->themeOverride = theme;
}
View::~View()
{
menu_animation_ctx_tag alphaTag = (uintptr_t) & this->alpha;
menu_animation_kill_by_tag(&alphaTag);
menu_animation_ctx_tag highlightTag = (uintptr_t) & this->highlightAlpha;
menu_animation_kill_by_tag(&highlightTag);
menu_animation_ctx_tag collapseTag = (uintptr_t) & this->collapseState;
menu_animation_kill_by_tag(&collapseTag);
// Parent userdata
if (this->parentUserdata)
{
free(this->parentUserdata);
this->parentUserdata = nullptr;
}
// Focus sanity check
if (Application::getCurrentFocus() == this)
Application::giveFocus(nullptr);
}
void View::invalidate(bool immediate)
{
if (immediate)
this->layout(Application::getNVGContext(), Application::getStyle(), Application::getFontStash());
else
this->dirty = true;
}
} // namespace brls

Some files were not shown because too many files have changed in this diff Show More