mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-04-22 04:24:48 +00:00
Implemented a system updater homebrew (titled Daybreak)
This commit is contained in:
parent
b08ccd7341
commit
561d0458ba
13 changed files with 2268 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -38,6 +38,9 @@
|
|||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Deko3d shaders
|
||||
*.dksh
|
||||
|
||||
# Switch Executables
|
||||
*.nso
|
||||
*.nro
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
APPLICATIONS := reboot_to_payload
|
||||
APPLICATIONS := daybreak reboot_to_payload
|
||||
|
||||
SUBFOLDERS := $(APPLICATIONS)
|
||||
|
||||
|
|
282
troposphere/daybreak/Makefile
Normal file
282
troposphere/daybreak/Makefile
Normal file
|
@ -0,0 +1,282 @@
|
|||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#
|
||||
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.json
|
||||
# - config.json
|
||||
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
|
||||
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
|
||||
# NACP building is skipped as well.
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := daybreak
|
||||
BUILD := build
|
||||
SOURCES := source nanovg/shaders
|
||||
DATA := data
|
||||
INCLUDES := include ../include
|
||||
ROMFS := romfs
|
||||
|
||||
# Output folders for autogenerated files in romfs
|
||||
OUT_SHADERS := shaders
|
||||
|
||||
APP_TITLE := Daybreak
|
||||
APP_AUTHOR := Atmosphere-NX
|
||||
APP_VERSION := 1.0.0
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -std=gnu++17 -fno-exceptions -fno-rtti
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lnanovg -ldeko3d -lnx
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/nanovg/
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
SUBFOLDERS := nanovg
|
||||
|
||||
TOPTARGETS := all clean
|
||||
|
||||
$(TOPTARGETS): $(SUBFOLDERS)
|
||||
|
||||
$(SUBFOLDERS):
|
||||
$(MAKE) -C $@ $(MAKECMDGOALS)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
ifneq ($(strip $(ROMFS)),)
|
||||
ROMFS_TARGETS :=
|
||||
ROMFS_FOLDERS :=
|
||||
ifneq ($(strip $(OUT_SHADERS)),)
|
||||
ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS)
|
||||
ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES))
|
||||
ROMFS_FOLDERS += $(ROMFS_SHADERS)
|
||||
endif
|
||||
|
||||
export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file))
|
||||
endif
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
ifeq ($(strip $(CONFIG_JSON)),)
|
||||
jsons := $(wildcard *.json)
|
||||
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||
else
|
||||
ifneq (,$(findstring config.json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/config.json
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(ICON)),)
|
||||
icons := $(wildcard *.jpg)
|
||||
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
|
||||
else
|
||||
ifneq (,$(findstring icon.jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/icon.jpg
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_ICON := $(TOPDIR)/$(ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_ICON)),)
|
||||
export NROFLAGS += --icon=$(APP_ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
|
||||
endif
|
||||
|
||||
ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(TOPTARGETS) $(SUBFOLDERS) all clean
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(ROMFS_TARGETS) | $(BUILD)
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
$(BUILD):
|
||||
@mkdir -p $@
|
||||
|
||||
ifneq ($(strip $(ROMFS_TARGETS)),)
|
||||
|
||||
$(ROMFS_TARGETS): | $(ROMFS_FOLDERS)
|
||||
|
||||
$(ROMFS_FOLDERS):
|
||||
@mkdir -p $@
|
||||
|
||||
$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl
|
||||
@echo {vert} $(notdir $<)
|
||||
@uam -s vert -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl
|
||||
@echo {tess_ctrl} $(notdir $<)
|
||||
@uam -s tess_ctrl -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl
|
||||
@echo {tess_eval} $(notdir $<)
|
||||
@uam -s tess_eval -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl
|
||||
@echo {geom} $(notdir $<)
|
||||
@uam -s geom -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl
|
||||
@echo {frag} $(notdir $<)
|
||||
@uam -s frag -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%.dksh: %.glsl
|
||||
@echo {comp} $(notdir $<)
|
||||
@uam -s comp -o $@ $<
|
||||
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
@rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
else
|
||||
@rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
||||
endif
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
.PHONY: all
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
|
||||
all : $(OUTPUT).nro
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
|
||||
else
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS)
|
||||
endif
|
||||
|
||||
else
|
||||
|
||||
all : $(OUTPUT).nsp
|
||||
|
||||
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
|
||||
endif
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
BIN
troposphere/daybreak/icon.jpg
Normal file
BIN
troposphere/daybreak/icon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
158
troposphere/daybreak/source/ams_su.c
Normal file
158
troposphere/daybreak/source/ams_su.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include "ams_su.h"
|
||||
#include "service_guard.h"
|
||||
|
||||
static Service g_amssuSrv;
|
||||
static TransferMemory g_tmem;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(amssu);
|
||||
|
||||
Result _amssuInitialize(void) {
|
||||
return smGetService(&g_amssuSrv, "ams:su");
|
||||
}
|
||||
|
||||
void _amssuCleanup(void) {
|
||||
serviceClose(&g_amssuSrv);
|
||||
tmemClose(&g_tmem);
|
||||
}
|
||||
|
||||
Service *amssuGetServiceSession(void) {
|
||||
return &g_amssuSrv;
|
||||
}
|
||||
|
||||
Result amssuGetUpdateInformation(AmsSuUpdateInformation *out, const char *path) {
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
return serviceDispatchOut(&g_amssuSrv, 0, *out,
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
}
|
||||
|
||||
Result amssuValidateUpdate(AmsSuUpdateValidationInfo *out, const char *path) {
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
return serviceDispatchOut(&g_amssuSrv, 1, *out,
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
}
|
||||
|
||||
Result amssuSetupUpdate(void *buffer, size_t size, const char *path, bool exfat) {
|
||||
Result rc = 0;
|
||||
|
||||
if (buffer == NULL) {
|
||||
rc = tmemCreate(&g_tmem, size, Perm_None);
|
||||
} else {
|
||||
rc = tmemCreateFromMemory(&g_tmem, buffer, size, Perm_None);
|
||||
}
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
const struct {
|
||||
u8 exfat;
|
||||
u64 size;
|
||||
} in = { exfat, g_tmem.size };
|
||||
|
||||
rc = serviceDispatchIn(&g_amssuSrv, 2, in,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { g_tmem.handle },
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
if (R_FAILED((rc))) {
|
||||
tmemClose(&g_tmem);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuSetupUpdateWithVariation(void *buffer, size_t size, const char *path, bool exfat, u32 variation) {
|
||||
Result rc = 0;
|
||||
|
||||
if (buffer == NULL) {
|
||||
rc = tmemCreate(&g_tmem, size, Perm_None);
|
||||
} else {
|
||||
rc = tmemCreateFromMemory(&g_tmem, buffer, size, Perm_None);
|
||||
}
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
const struct {
|
||||
u8 exfat;
|
||||
u32 variation;
|
||||
u64 size;
|
||||
} in = { exfat, variation, g_tmem.size };
|
||||
|
||||
rc = serviceDispatchIn(&g_amssuSrv, 3, in,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { g_tmem.handle },
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
if (R_FAILED((rc))) {
|
||||
tmemClose(&g_tmem);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuRequestPrepareUpdate(AsyncResult *a) {
|
||||
memset(a, 0, sizeof(*a));
|
||||
|
||||
Handle event = INVALID_HANDLE;
|
||||
Result rc = serviceDispatch(&g_amssuSrv, 4,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = &a->s,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = &event,
|
||||
);
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
eventLoadRemote(&a->event, event, false);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuGetPrepareUpdateProgress(NsSystemUpdateProgress *out) {
|
||||
return serviceDispatchOut(&g_amssuSrv, 5, *out);
|
||||
}
|
||||
|
||||
Result amssuHasPreparedUpdate(bool *out) {
|
||||
u8 outval = 0;
|
||||
Result rc = serviceDispatchOut(&g_amssuSrv, 6, outval);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out) *out = outval & 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuApplyPreparedUpdate() {
|
||||
return serviceDispatch(&g_amssuSrv, 7);
|
||||
}
|
51
troposphere/daybreak/source/ams_su.h
Normal file
51
troposphere/daybreak/source/ams_su.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
bool exfat_supported;
|
||||
u32 num_firmware_variations;
|
||||
u32 firmware_variation_ids[16];
|
||||
} AmsSuUpdateInformation;
|
||||
|
||||
typedef struct {
|
||||
Result result;
|
||||
NcmContentMetaKey invalid_key;
|
||||
NcmContentId invalid_content_id;
|
||||
} AmsSuUpdateValidationInfo;
|
||||
|
||||
Result amssuInitialize();
|
||||
void amssuExit();
|
||||
Service *amssuGetServiceSession(void);
|
||||
|
||||
Result amssuGetUpdateInformation(AmsSuUpdateInformation *out, const char *path);
|
||||
Result amssuValidateUpdate(AmsSuUpdateValidationInfo *out, const char *path);
|
||||
Result amssuSetupUpdate(void *buffer, size_t size, const char *path, bool exfat);
|
||||
Result amssuSetupUpdateWithVariation(void *buffer, size_t size, const char *path, bool exfat, u32 variation);
|
||||
Result amssuRequestPrepareUpdate(AsyncResult *a);
|
||||
Result amssuGetPrepareUpdateProgress(NsSystemUpdateProgress *out);
|
||||
Result amssuHasPreparedUpdate(bool *out);
|
||||
Result amssuApplyPreparedUpdate();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
23
troposphere/daybreak/source/assert.hpp
Normal file
23
troposphere/daybreak/source/assert.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#define DBK_ABORT_UNLESS(expr) \
|
||||
if (!static_cast<bool>(expr)) { \
|
||||
std::abort(); \
|
||||
}
|
257
troposphere/daybreak/source/main.cpp
Normal file
257
troposphere/daybreak/source/main.cpp
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <optional>
|
||||
#include <switch.h>
|
||||
#include <nanovg.h>
|
||||
#include <nanovg_dk.h>
|
||||
#include <nanovg/framework/CApplication.h>
|
||||
#include "ui.hpp"
|
||||
#include "ams_su.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void userAppInit(void) {
|
||||
Result rc = 0;
|
||||
|
||||
if (R_FAILED(rc = amssuInitialize())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = romfsInit())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = spsmInitialize())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = plInitialize(PlServiceType_User))) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
}
|
||||
|
||||
void userAppExit(void) {
|
||||
romfsExit();
|
||||
plExit();
|
||||
spsmExit();
|
||||
amssuExit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr u32 FramebufferWidth = 1280;
|
||||
static constexpr u32 FramebufferHeight = 720;
|
||||
|
||||
}
|
||||
|
||||
class Daybreak : public CApplication {
|
||||
private:
|
||||
static constexpr unsigned NumFramebuffers = 2;
|
||||
static constexpr unsigned StaticCmdSize = 0x1000;
|
||||
|
||||
dk::UniqueDevice m_device;
|
||||
dk::UniqueQueue m_queue;
|
||||
dk::UniqueSwapchain m_swapchain;
|
||||
|
||||
std::optional<CMemPool> m_pool_images;
|
||||
std::optional<CMemPool> m_pool_code;
|
||||
std::optional<CMemPool> m_pool_data;
|
||||
|
||||
dk::UniqueCmdBuf m_cmd_buf;
|
||||
DkCmdList m_render_cmdlist;
|
||||
|
||||
dk::Image m_depth_buffer;
|
||||
CMemPool::Handle m_depth_buffer_mem;
|
||||
dk::Image m_framebuffers[NumFramebuffers];
|
||||
CMemPool::Handle m_framebuffers_mem[NumFramebuffers];
|
||||
DkCmdList m_framebuffer_cmdlists[NumFramebuffers];
|
||||
|
||||
std::optional<nvg::DkRenderer> m_renderer;
|
||||
NVGcontext *m_vg;
|
||||
int m_standard_font;
|
||||
public:
|
||||
Daybreak() {
|
||||
Result rc = 0;
|
||||
|
||||
/* Create the deko3d device. */
|
||||
m_device = dk::DeviceMaker{}.create();
|
||||
|
||||
/* Create the main queue. */
|
||||
m_queue = dk::QueueMaker{m_device}.setFlags(DkQueueFlags_Graphics).create();
|
||||
|
||||
/* Create the memory pools. */
|
||||
m_pool_images.emplace(m_device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024);
|
||||
m_pool_code.emplace(m_device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024);
|
||||
m_pool_data.emplace(m_device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024);
|
||||
|
||||
/* Create the static command buffer and feed it freshly allocated memory. */
|
||||
m_cmd_buf = dk::CmdBufMaker{m_device}.create();
|
||||
CMemPool::Handle cmdmem = m_pool_data->allocate(StaticCmdSize);
|
||||
m_cmd_buf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize());
|
||||
|
||||
/* Create the framebuffer resources. */
|
||||
this->CreateFramebufferResources();
|
||||
|
||||
m_renderer.emplace(FramebufferWidth, FramebufferHeight, m_device, m_queue, *m_pool_images, *m_pool_code, *m_pool_data);
|
||||
m_vg = nvgCreateDk(&*m_renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES);
|
||||
|
||||
|
||||
PlFontData font;
|
||||
if (R_FAILED(rc = plGetSharedFontByType(&font, PlSharedFontType_Standard))) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
m_standard_font = nvgCreateFontMem(m_vg, "switch-standard", static_cast<u8 *>(font.address), font.size, 0);
|
||||
}
|
||||
|
||||
~Daybreak() {
|
||||
/* Destroy the framebuffer resources. This should be done first. */
|
||||
this->DestroyFramebufferResources();
|
||||
|
||||
/* Cleanup vg. */
|
||||
nvgDeleteDk(m_vg);
|
||||
|
||||
/* Destroy the renderer. */
|
||||
m_renderer.reset();
|
||||
}
|
||||
private:
|
||||
void CreateFramebufferResources() {
|
||||
/* Create layout for the depth buffer. */
|
||||
dk::ImageLayout layout_depth_buffer;
|
||||
dk::ImageLayoutMaker{m_device}
|
||||
.setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression)
|
||||
.setFormat(DkImageFormat_S8)
|
||||
.setDimensions(FramebufferWidth, FramebufferHeight)
|
||||
.initialize(layout_depth_buffer);
|
||||
|
||||
/* Create the depth buffer. */
|
||||
m_depth_buffer_mem = m_pool_images->allocate(layout_depth_buffer.getSize(), layout_depth_buffer.getAlignment());
|
||||
m_depth_buffer.initialize(layout_depth_buffer, m_depth_buffer_mem.getMemBlock(), m_depth_buffer_mem.getOffset());
|
||||
|
||||
/* Create layout for the framebuffers. */
|
||||
dk::ImageLayout layout_framebuffer;
|
||||
dk::ImageLayoutMaker{m_device}
|
||||
.setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression)
|
||||
.setFormat(DkImageFormat_RGBA8_Unorm)
|
||||
.setDimensions(FramebufferWidth, FramebufferHeight)
|
||||
.initialize(layout_framebuffer);
|
||||
|
||||
/* Create the framebuffers. */
|
||||
std::array<DkImage const*, NumFramebuffers> fb_array;
|
||||
const u64 fb_size = layout_framebuffer.getSize();
|
||||
const u32 fb_align = layout_framebuffer.getAlignment();
|
||||
|
||||
for (unsigned int i = 0; i < NumFramebuffers; i++) {
|
||||
/* Allocate a framebuffer. */
|
||||
m_framebuffers_mem[i] = m_pool_images->allocate(fb_size, fb_align);
|
||||
m_framebuffers[i].initialize(layout_framebuffer, m_framebuffers_mem[i].getMemBlock(), m_framebuffers_mem[i].getOffset());
|
||||
|
||||
/* Generate a command list that binds it. */
|
||||
dk::ImageView color_target{ m_framebuffers[i] }, depth_target{ m_depth_buffer };
|
||||
m_cmd_buf.bindRenderTargets(&color_target, &depth_target);
|
||||
m_framebuffer_cmdlists[i] = m_cmd_buf.finishList();
|
||||
|
||||
/* Fill in the array for use later by the swapchain creation code. */
|
||||
fb_array[i] = &m_framebuffers[i];
|
||||
}
|
||||
|
||||
/* Create the swapchain using the framebuffers. */
|
||||
m_swapchain = dk::SwapchainMaker{m_device, nwindowGetDefault(), fb_array}.create();
|
||||
|
||||
/* Generate the main rendering cmdlist. */
|
||||
this->RecordStaticCommands();
|
||||
}
|
||||
|
||||
void DestroyFramebufferResources() {
|
||||
/* Return early if we have nothing to destroy. */
|
||||
if (!m_swapchain) return;
|
||||
|
||||
/* Make sure the queue is idle before destroying anything. */
|
||||
m_queue.waitIdle();
|
||||
|
||||
/* Clear the static cmdbuf, destroying the static cmdlists in the process. */
|
||||
m_cmd_buf.clear();
|
||||
|
||||
/* Destroy the swapchain. */
|
||||
m_swapchain.destroy();
|
||||
|
||||
/* Destroy the framebuffers. */
|
||||
for (unsigned int i = 0; i < NumFramebuffers; i ++) {
|
||||
m_framebuffers_mem[i].destroy();
|
||||
}
|
||||
|
||||
/* Destroy the depth buffer. */
|
||||
m_depth_buffer_mem.destroy();
|
||||
}
|
||||
|
||||
void RecordStaticCommands() {
|
||||
/* Initialize state structs with deko3d defaults. */
|
||||
dk::RasterizerState rasterizer_state;
|
||||
dk::ColorState color_state;
|
||||
dk::ColorWriteState color_write_state;
|
||||
|
||||
/* Configure the viewport and scissor. */
|
||||
m_cmd_buf.setViewports(0, { { 0.0f, 0.0f, FramebufferWidth, FramebufferHeight, 0.0f, 1.0f } });
|
||||
m_cmd_buf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } });
|
||||
|
||||
/* Clear the color and depth buffers. */
|
||||
m_cmd_buf.clearColor(0, DkColorMask_RGBA, 0.f, 0.f, 0.f, 1.0f);
|
||||
m_cmd_buf.clearDepthStencil(true, 1.0f, 0xFF, 0);
|
||||
|
||||
/* Bind required state. */
|
||||
m_cmd_buf.bindRasterizerState(rasterizer_state);
|
||||
m_cmd_buf.bindColorState(color_state);
|
||||
m_cmd_buf.bindColorWriteState(color_write_state);
|
||||
|
||||
m_render_cmdlist = m_cmd_buf.finishList();
|
||||
}
|
||||
|
||||
void Render(u64 ns) {
|
||||
/* Acquire a framebuffer from the swapchain (and wait for it to be available). */
|
||||
int slot = m_queue.acquireImage(m_swapchain);
|
||||
|
||||
/* Run the command list that attaches said framebuffer to the queue. */
|
||||
m_queue.submitCommands(m_framebuffer_cmdlists[slot]);
|
||||
|
||||
/* Run the main rendering command list. */
|
||||
m_queue.submitCommands(m_render_cmdlist);
|
||||
|
||||
nvgBeginFrame(m_vg, FramebufferWidth, FramebufferHeight, 1.0f);
|
||||
dbk::RenderMenu(m_vg, ns);
|
||||
nvgEndFrame(m_vg);
|
||||
|
||||
/* Now that we are done rendering, present it to the screen. */
|
||||
m_queue.presentImage(m_swapchain, slot);
|
||||
}
|
||||
|
||||
public:
|
||||
bool onFrame(u64 ns) override {
|
||||
dbk::UpdateMenu(ns);
|
||||
this->Render(ns);
|
||||
return !dbk::IsExitRequested();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* Initialize the menu. */
|
||||
dbk::InitializeMenu(FramebufferWidth, FramebufferHeight);
|
||||
|
||||
Daybreak daybreak;
|
||||
daybreak.run();
|
||||
return 0;
|
||||
}
|
56
troposphere/daybreak/source/service_guard.h
Normal file
56
troposphere/daybreak/source/service_guard.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
#include <switch/types.h>
|
||||
#include <switch/result.h>
|
||||
#include <switch/kernel/mutex.h>
|
||||
#include <switch/sf/service.h>
|
||||
#include <switch/services/sm.h>
|
||||
|
||||
typedef struct ServiceGuard {
|
||||
Mutex mutex;
|
||||
u32 refCount;
|
||||
} ServiceGuard;
|
||||
|
||||
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
return (g->refCount++) == 0;
|
||||
}
|
||||
|
||||
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
|
||||
{
|
||||
if (R_FAILED(rc)) {
|
||||
cleanupFunc();
|
||||
--g->refCount;
|
||||
}
|
||||
mutexUnlock(&g->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
if (g->refCount && (--g->refCount) == 0)
|
||||
cleanupFunc();
|
||||
mutexUnlock(&g->mutex);
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
|
||||
\
|
||||
static ServiceGuard g_##name##Guard; \
|
||||
NX_INLINE Result _##name##Initialize _paramdecl; \
|
||||
static void _##name##Cleanup(void); \
|
||||
\
|
||||
Result name##Initialize _paramdecl \
|
||||
{ \
|
||||
Result rc = 0; \
|
||||
if (serviceGuardBeginInit(&g_##name##Guard)) \
|
||||
rc = _##name##Initialize _parampass; \
|
||||
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
|
||||
} \
|
||||
\
|
||||
void name##Exit(void) \
|
||||
{ \
|
||||
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
|
966
troposphere/daybreak/source/ui.cpp
Normal file
966
troposphere/daybreak/source/ui.cpp
Normal file
|
@ -0,0 +1,966 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include "ui.hpp"
|
||||
#include "ui_util.hpp"
|
||||
#include "assert.hpp"
|
||||
|
||||
namespace dbk {
|
||||
|
||||
namespace {
|
||||
|
||||
u32 g_screen_width;
|
||||
u32 g_screen_height;
|
||||
|
||||
std::shared_ptr<Menu> g_current_menu;
|
||||
bool g_initialized = false;
|
||||
bool g_exit_requested = false;
|
||||
|
||||
u32 g_prev_touch_count = -1;
|
||||
touchPosition g_start_touch_position;
|
||||
bool g_started_touching = false;
|
||||
bool g_tapping = false;
|
||||
bool g_touches_moving = false;
|
||||
bool g_finished_touching = false;
|
||||
|
||||
/* Update install state. */
|
||||
char g_update_path[FS_MAX_PATH];
|
||||
bool g_use_exfat = false;
|
||||
|
||||
constexpr u32 MaxTapMovement = 20;
|
||||
|
||||
void UpdateInput() {
|
||||
/* Update the previous touch count. */
|
||||
g_prev_touch_count = hidTouchCount();
|
||||
|
||||
/* Scan for input and update touch state. */
|
||||
hidScanInput();
|
||||
const u32 touch_count = hidTouchCount();
|
||||
|
||||
if (g_prev_touch_count == 0 && touch_count > 0) {
|
||||
hidTouchRead(&g_start_touch_position, 0);
|
||||
g_started_touching = true;
|
||||
g_tapping = true;
|
||||
} else {
|
||||
g_started_touching = false;
|
||||
}
|
||||
|
||||
if (g_prev_touch_count > 0 && touch_count == 0) {
|
||||
g_finished_touching = true;
|
||||
g_tapping = false;
|
||||
} else {
|
||||
g_finished_touching = false;
|
||||
}
|
||||
|
||||
/* Check if currently moving. */
|
||||
if (g_prev_touch_count > 0 && touch_count > 0) {
|
||||
touchPosition current_touch_position;
|
||||
hidTouchRead(¤t_touch_position, 0);
|
||||
|
||||
if ((abs(current_touch_position.px - g_start_touch_position.px) > MaxTapMovement || abs(current_touch_position.py - g_start_touch_position.py) > MaxTapMovement)) {
|
||||
g_touches_moving = true;
|
||||
g_tapping = false;
|
||||
} else {
|
||||
g_touches_moving = false;
|
||||
}
|
||||
} else {
|
||||
g_touches_moving = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeMenu(std::shared_ptr<Menu> menu) {
|
||||
g_current_menu = menu;
|
||||
}
|
||||
|
||||
void ReturnToPreviousMenu() {
|
||||
/* Go to the previous menu if there is one. */
|
||||
if (g_current_menu->GetPrevMenu() != nullptr) {
|
||||
g_current_menu = g_current_menu->GetPrevMenu();
|
||||
}
|
||||
}
|
||||
|
||||
Result IsPathBottomLevel(const char *path, bool *out) {
|
||||
Result rc = 0;
|
||||
FsFileSystem *fs;
|
||||
char translated_path[FS_MAX_PATH] = {};
|
||||
DBK_ABORT_UNLESS(fsdevTranslatePath(path, &fs, translated_path) != -1);
|
||||
|
||||
FsDir dir;
|
||||
if (R_FAILED(rc = fsFsOpenDirectory(fs, translated_path, FsDirOpenMode_ReadDirs, &dir))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
s64 entry_count;
|
||||
if (R_FAILED(rc = fsDirGetEntryCount(&dir, &entry_count))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
*out = entry_count == 0;
|
||||
fsDirClose(&dir);
|
||||
return rc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Menu::AddButton(u32 id, const char *text, float x, float y, float w, float h) {
|
||||
DBK_ABORT_UNLESS(id < MaxButtons);
|
||||
Button button = {
|
||||
.id = id,
|
||||
.selected = false,
|
||||
.enabled = true,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.w = w,
|
||||
.h = h,
|
||||
};
|
||||
|
||||
strncpy(button.text, text, sizeof(button.text)-1);
|
||||
m_buttons[id] = button;
|
||||
}
|
||||
|
||||
void Menu::SetButtonSelected(u32 id, bool selected) {
|
||||
DBK_ABORT_UNLESS(id < MaxButtons);
|
||||
auto &button = m_buttons[id];
|
||||
|
||||
if (button) {
|
||||
button->selected = selected;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::DeselectAllButtons() {
|
||||
for (auto &button : m_buttons) {
|
||||
/* Ensure button is present. */
|
||||
if (!button) {
|
||||
continue;
|
||||
}
|
||||
button->selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::SetButtonEnabled(u32 id, bool enabled) {
|
||||
DBK_ABORT_UNLESS(id < MaxButtons);
|
||||
auto &button = m_buttons[id];
|
||||
button->enabled = enabled;
|
||||
}
|
||||
|
||||
Button *Menu::GetButton(u32 id) {
|
||||
DBK_ABORT_UNLESS(id < MaxButtons);
|
||||
return !m_buttons[id] ? nullptr : &(*m_buttons[id]);
|
||||
}
|
||||
|
||||
Button *Menu::GetSelectedButton() {
|
||||
for (auto &button : m_buttons) {
|
||||
if (button && button->enabled && button->selected) {
|
||||
return &(*button);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Button *Menu::GetClosestButtonToSelection(Direction direction) {
|
||||
const Button *selected_button = this->GetSelectedButton();
|
||||
|
||||
if (selected_button == nullptr || direction == Direction::Invalid) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Button *closest_button = nullptr;
|
||||
float closest_distance = 0.0f;
|
||||
|
||||
for (auto &button : m_buttons) {
|
||||
/* Skip absent button. */
|
||||
if (!button || !button->enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip buttons that are in the wrong direction. */
|
||||
if ((direction == Direction::Down && button->y <= selected_button->y) ||
|
||||
(direction == Direction::Up && button->y >= selected_button->y) ||
|
||||
(direction == Direction::Right && button->x <= selected_button->x) ||
|
||||
(direction == Direction::Left && button->x >= selected_button->x)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float x_dist = button->x - selected_button->x;
|
||||
const float y_dist = button->y - selected_button->y;
|
||||
const float sq_dist = x_dist * x_dist + y_dist * y_dist;
|
||||
|
||||
/* If we don't already have a closest button, set it. */
|
||||
if (closest_button == nullptr) {
|
||||
closest_button = &(*button);
|
||||
closest_distance = sq_dist;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Update the closest button if this one is closer. */
|
||||
if (sq_dist < closest_distance) {
|
||||
closest_button = &(*button);
|
||||
closest_distance = sq_dist;
|
||||
}
|
||||
}
|
||||
|
||||
return closest_button;
|
||||
}
|
||||
|
||||
Button *Menu::GetTouchedButton() {
|
||||
touchPosition touch;
|
||||
const u32 touch_count = hidTouchCount();
|
||||
|
||||
for (u32 i = 0; i < touch_count && g_started_touching; i++) {
|
||||
hidTouchRead(&touch, i);
|
||||
|
||||
for (auto &button : m_buttons) {
|
||||
if (button && button->enabled && button->IsPositionInBounds(touch.px, touch.py)) {
|
||||
return &(*button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Button *Menu::GetActivatedButton() {
|
||||
Button *selected_button = this->GetSelectedButton();
|
||||
|
||||
if (selected_button == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const u64 k_down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
|
||||
if (k_down & KEY_A || this->GetTouchedButton() == selected_button) {
|
||||
return selected_button;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Menu::UpdateButtons() {
|
||||
const u64 k_down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
Direction direction = Direction::Invalid;
|
||||
|
||||
if (k_down & KEY_DOWN) {
|
||||
direction = Direction::Down;
|
||||
} else if (k_down & KEY_UP) {
|
||||
direction = Direction::Up;
|
||||
} else if (k_down & KEY_LEFT) {
|
||||
direction = Direction::Left;
|
||||
} else if (k_down & KEY_RIGHT) {
|
||||
direction = Direction::Right;
|
||||
}
|
||||
|
||||
/* Select the closest button. */
|
||||
if (const Button *closest_button = this->GetClosestButtonToSelection(direction); closest_button != nullptr) {
|
||||
this->DeselectAllButtons();
|
||||
this->SetButtonSelected(closest_button->id, true);
|
||||
}
|
||||
|
||||
/* Select the touched button. */
|
||||
if (const Button *touched_button = this->GetTouchedButton(); touched_button != nullptr) {
|
||||
this->DeselectAllButtons();
|
||||
this->SetButtonSelected(touched_button->id, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::DrawButtons(NVGcontext *vg, u64 ns) {
|
||||
for (auto &button : m_buttons) {
|
||||
/* Ensure button is present. */
|
||||
if (!button) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set the button style. */
|
||||
auto style = ButtonStyle::StandardDisabled;
|
||||
if (button->enabled) {
|
||||
style = button->selected ? ButtonStyle::StandardSelected : ButtonStyle::Standard;
|
||||
}
|
||||
|
||||
DrawButton(vg, button->text, button->x, button->y, button->w, button->h, style, ns);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::LogText(const char *format, ...) {
|
||||
/* Create a temporary string. */
|
||||
char tmp[0x100];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(tmp, sizeof(tmp)-1, format, args);
|
||||
va_end(args);
|
||||
|
||||
/* Append the text to the log buffer. */
|
||||
strncat(m_log_buffer, tmp, sizeof(m_log_buffer)-1);
|
||||
}
|
||||
|
||||
std::shared_ptr<Menu> Menu::GetPrevMenu() {
|
||||
return m_prev_menu;
|
||||
}
|
||||
|
||||
MainMenu::MainMenu() : Menu(nullptr) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
this->AddButton(InstallButtonId, "Install", x + ButtonHorizontalPadding, y + TitleGap, WindowWidth - ButtonHorizontalPadding * 2, ButtonHeight);
|
||||
this->AddButton(ExitButtonId, "Exit", x + ButtonHorizontalPadding, y + TitleGap + ButtonHeight + ButtonVerticalGap, WindowWidth - ButtonHorizontalPadding * 2, ButtonHeight);
|
||||
this->SetButtonSelected(InstallButtonId, true);
|
||||
}
|
||||
|
||||
void MainMenu::Update(u64 ns) {
|
||||
u64 k_down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
|
||||
if (k_down & KEY_B) {
|
||||
g_exit_requested = true;
|
||||
}
|
||||
|
||||
/* Take action if a button has been activated. */
|
||||
if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
|
||||
switch (activated_button->id) {
|
||||
case InstallButtonId:
|
||||
ChangeMenu(std::make_shared<FileMenu>(g_current_menu, "/"));
|
||||
break;
|
||||
case ExitButtonId:
|
||||
g_exit_requested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->UpdateButtons();
|
||||
|
||||
/* Fallback on selecting the install button. */
|
||||
if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
|
||||
this->SetButtonSelected(InstallButtonId, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenu::Draw(NVGcontext *vg, u64 ns) {
|
||||
DrawWindow(vg, "Daybreak", g_screen_width / 2.0f - WindowWidth / 2.0f, g_screen_height / 2.0f - WindowHeight / 2.0f, WindowWidth, WindowHeight);
|
||||
this->DrawButtons(vg, ns);
|
||||
}
|
||||
|
||||
FileMenu::FileMenu(std::shared_ptr<Menu> prev_menu, const char *root) : Menu(prev_menu), m_current_index(0), m_scroll_offset(0), m_touch_start_scroll_offset(0), m_touch_finalize_selection(false) {
|
||||
Result rc = 0;
|
||||
|
||||
strncpy(m_root, root, sizeof(m_root)-1);
|
||||
|
||||
if (R_FAILED(rc = this->PopulateFileEntries())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
}
|
||||
|
||||
Result FileMenu::PopulateFileEntries() {
|
||||
/* Open the directory. */
|
||||
DIR *dir = opendir(m_root);
|
||||
if (dir == nullptr) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
/* Add file entries to the list. */
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir)) != nullptr) {
|
||||
if (ent->d_type == DT_DIR) {
|
||||
FileEntry file_entry = {};
|
||||
strncpy(file_entry.name, ent->d_name, sizeof(file_entry.name));
|
||||
m_file_entries.push_back(file_entry);
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the directory. */
|
||||
closedir(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FileMenu::IsSelectionVisible() {
|
||||
const float visible_start = m_scroll_offset;
|
||||
const float visible_end = visible_start + FileListHeight;
|
||||
const float entry_start = static_cast<float>(m_current_index) * (FileRowHeight + FileRowGap);
|
||||
const float entry_end = entry_start + (FileRowHeight + FileRowGap);
|
||||
return entry_start >= visible_start && entry_end <= visible_end;
|
||||
}
|
||||
|
||||
void FileMenu::ScrollToSelection() {
|
||||
const float visible_start = m_scroll_offset;
|
||||
const float visible_end = visible_start + FileListHeight;
|
||||
const float entry_start = static_cast<float>(m_current_index) * (FileRowHeight + FileRowGap);
|
||||
const float entry_end = entry_start + (FileRowHeight + FileRowGap);
|
||||
|
||||
if (entry_end > visible_end) {
|
||||
m_scroll_offset += entry_end - visible_end;
|
||||
} else if (entry_end < visible_end) {
|
||||
m_scroll_offset = entry_start;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileMenu::IsEntryTouched(u32 i) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
touchPosition current_pos;
|
||||
hidTouchRead(¤t_pos, 0);
|
||||
|
||||
/* Check if the tap is within the x bounds. */
|
||||
if (current_pos.px >= x + TextBackgroundOffset + FileRowHorizontalInset && current_pos.px <= WindowWidth - (TextBackgroundOffset + FileRowHorizontalInset) * 2.0f) {
|
||||
const float y_min = y + TitleGap + FileRowGap + i * (FileRowHeight + FileRowGap) - m_scroll_offset;
|
||||
const float y_max = y_min + FileRowHeight;
|
||||
|
||||
/* Check if the tap is within the y bounds. */
|
||||
if (current_pos.py >= y_min && current_pos.py <= y_max) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileMenu::UpdateTouches() {
|
||||
/* Setup values on initial touch. */
|
||||
if (g_started_touching) {
|
||||
m_touch_start_scroll_offset = m_scroll_offset;
|
||||
|
||||
/* We may potentially finalize the selection later if we start off touching it. */
|
||||
if (this->IsEntryTouched(m_current_index)) {
|
||||
m_touch_finalize_selection = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scroll based on touch movement. */
|
||||
if (g_touches_moving) {
|
||||
touchPosition current_pos;
|
||||
hidTouchRead(¤t_pos, 0);
|
||||
|
||||
const int dist_y = current_pos.py - g_start_touch_position.py;
|
||||
float new_scroll_offset = m_touch_start_scroll_offset - static_cast<float>(dist_y);
|
||||
float max_scroll = (FileRowHeight + FileRowGap) * static_cast<float>(m_file_entries.size()) - FileListHeight;
|
||||
|
||||
/* Don't allow scrolling if there is not enough elements. */
|
||||
if (max_scroll < 0.0f) {
|
||||
max_scroll = 0.0f;
|
||||
}
|
||||
|
||||
/* Don't allow scrolling before the first element. */
|
||||
if (new_scroll_offset < 0.0f) {
|
||||
new_scroll_offset = 0.0f;
|
||||
}
|
||||
|
||||
/* Don't allow scrolling past the last element. */
|
||||
if (new_scroll_offset > max_scroll) {
|
||||
new_scroll_offset = max_scroll;
|
||||
}
|
||||
|
||||
m_scroll_offset = new_scroll_offset;
|
||||
}
|
||||
|
||||
/* Select any tapped entries. */
|
||||
if (g_tapping) {
|
||||
for (u32 i = 0; i < m_file_entries.size(); i++) {
|
||||
if (this->IsEntryTouched(i)) {
|
||||
/* The current index is checked later. */
|
||||
if (i == m_current_index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_current_index = i;
|
||||
|
||||
/* Don't finalize selection if we touch something else. */
|
||||
m_touch_finalize_selection = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't finalize selection if we aren't finished and we've either stopped tapping or are no longer touching the selection. */
|
||||
if (!g_finished_touching && (!g_tapping || !this->IsEntryTouched(m_current_index))) {
|
||||
m_touch_finalize_selection = false;
|
||||
}
|
||||
|
||||
/* Finalize selection if the currently selected entry is touched for the second time. */
|
||||
if (g_finished_touching && m_touch_finalize_selection) {
|
||||
this->FinalizeSelection();
|
||||
m_touch_finalize_selection = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::FinalizeSelection() {
|
||||
DBK_ABORT_UNLESS(m_current_index < m_file_entries.size());
|
||||
FileEntry &entry = m_file_entries[m_current_index];
|
||||
|
||||
/* Determine the selected path. */
|
||||
char current_path[FS_MAX_PATH] = {};
|
||||
snprintf(current_path, sizeof(current_path)-1, "%s%s", m_root, entry.name);
|
||||
|
||||
/* Determine if the chosen path is the bottom level. */
|
||||
Result rc = 0;
|
||||
bool bottom_level;
|
||||
if (R_FAILED(rc = IsPathBottomLevel(current_path, &bottom_level))) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
/* Show exfat settings or the next file menu. */
|
||||
if (bottom_level) {
|
||||
/* Set the update path. */
|
||||
snprintf(g_update_path, sizeof(g_update_path)-1, "%s", current_path);
|
||||
|
||||
/* Change the menu. */
|
||||
ChangeMenu(std::make_shared<ValidateUpdateMenu>(g_current_menu));
|
||||
} else {
|
||||
ChangeMenu(std::make_shared<FileMenu>(g_current_menu, current_path));
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::Update(u64 ns) {
|
||||
u64 k_down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
|
||||
/* Go back if B is pressed. */
|
||||
if (k_down & KEY_B) {
|
||||
ReturnToPreviousMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Finalize selection on pressing A. */
|
||||
if (k_down & KEY_A) {
|
||||
this->FinalizeSelection();
|
||||
}
|
||||
|
||||
/* Update touch input. */
|
||||
this->UpdateTouches();
|
||||
|
||||
const u32 prev_index = m_current_index;
|
||||
|
||||
if (k_down & KEY_DOWN) {
|
||||
/* Scroll down. */
|
||||
if (m_current_index >= (m_file_entries.size() - 1)) {
|
||||
m_current_index = 0;
|
||||
} else {
|
||||
m_current_index++;
|
||||
}
|
||||
} else if (k_down & KEY_UP) {
|
||||
/* Scroll up. */
|
||||
if (m_current_index == 0) {
|
||||
m_current_index = m_file_entries.size() - 1;
|
||||
} else {
|
||||
m_current_index--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scroll to the selection if it isn't visible. */
|
||||
if (prev_index != m_current_index && !this->IsSelectionVisible()) {
|
||||
this->ScrollToSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::Draw(NVGcontext *vg, u64 ns) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
DrawWindow(vg, "Select an update directory", x, y, WindowWidth, WindowHeight);
|
||||
DrawTextBackground(vg, x + TextBackgroundOffset, y + TitleGap, WindowWidth - TextBackgroundOffset * 2.0f, (FileRowHeight + FileRowGap) * MaxFileRows + FileRowGap);
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, x + TextBackgroundOffset, y + TitleGap, WindowWidth - TextBackgroundOffset * 2.0f, (FileRowHeight + FileRowGap) * MaxFileRows + FileRowGap);
|
||||
|
||||
for (u32 i = 0; i < m_file_entries.size(); i++) {
|
||||
FileEntry &entry = m_file_entries[i];
|
||||
auto style = ButtonStyle::FileSelect;
|
||||
|
||||
if (i == m_current_index) {
|
||||
style = ButtonStyle::FileSelectSelected;
|
||||
}
|
||||
|
||||
DrawButton(vg, entry.name, x + TextBackgroundOffset + FileRowHorizontalInset, y + TitleGap + FileRowGap + i * (FileRowHeight + FileRowGap) - m_scroll_offset, WindowWidth - (TextBackgroundOffset + FileRowHorizontalInset) * 2.0f, FileRowHeight, style, ns);
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
}
|
||||
|
||||
ValidateUpdateMenu::ValidateUpdateMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu), m_has_drawn(false), m_has_info(false), m_has_validated(false) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
/* Add buttons. */
|
||||
this->AddButton(BackButtonId, "Back", x + HorizontalGap, y + WindowHeight - BottomGap - ButtonHeight, ButtonWidth, ButtonHeight);
|
||||
this->AddButton(ContinueButtonId, "Continue", x + HorizontalGap + ButtonWidth + ButtonHorizontalGap, y + WindowHeight - BottomGap - ButtonHeight, ButtonWidth, ButtonHeight);
|
||||
this->SetButtonEnabled(BackButtonId, false);
|
||||
this->SetButtonEnabled(ContinueButtonId, false);
|
||||
|
||||
/* Obtain update information. */
|
||||
if (R_FAILED(this->GetUpdateInformation())) {
|
||||
this->SetButtonEnabled(BackButtonId, true);
|
||||
this->SetButtonSelected(BackButtonId, true);
|
||||
} else {
|
||||
/* Log this early so it is printed out before validation causes stalling. */
|
||||
this->LogText("Validating update, this may take a moment...\n");
|
||||
}
|
||||
}
|
||||
|
||||
Result ValidateUpdateMenu::GetUpdateInformation() {
|
||||
Result rc = 0;
|
||||
this->LogText("Directory %s\n", g_update_path);
|
||||
|
||||
/* Attempt to get the update information. */
|
||||
if (R_FAILED(rc = amssuGetUpdateInformation(&m_update_info, g_update_path))) {
|
||||
if (rc == 0x1a405) {
|
||||
this->LogText("No update found in folder.\nEnsure your ncas are named correctly!\nResult: 0x%08x\n", rc);
|
||||
} else {
|
||||
this->LogText("Failed to get update information.\nResult: 0x%08x\n", rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Print update information. */
|
||||
this->LogText("- Version: %d.%d.%d\n", (m_update_info.version >> 26) & 0x1f, (m_update_info.version >> 20) & 0x1f, (m_update_info.version >> 16) & 0xf);
|
||||
if (m_update_info.exfat_supported) {
|
||||
this->LogText("- exFAT: Supported\n");
|
||||
} else {
|
||||
this->LogText("- exFAT: Unsupported\n");
|
||||
}
|
||||
this->LogText("- Firmware variations: %d\n", m_update_info.num_firmware_variations);
|
||||
|
||||
/* Mark as having obtained update info. */
|
||||
m_has_info = true;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void ValidateUpdateMenu::ValidateUpdate() {
|
||||
Result rc = 0;
|
||||
|
||||
/* Validate the update. */
|
||||
if (R_FAILED(rc = amssuValidateUpdate(&m_validation_info, g_update_path))) {
|
||||
this->LogText("Failed to validate update.\nResult: 0x%08x\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check the result. */
|
||||
if (R_SUCCEEDED(m_validation_info.result)) {
|
||||
this->LogText("Update is valid!\n");
|
||||
|
||||
/* Enable the back and continue buttons and select the continue button. */
|
||||
this->SetButtonEnabled(BackButtonId, true);
|
||||
this->SetButtonEnabled(ContinueButtonId, true);
|
||||
this->SetButtonSelected(ContinueButtonId, true);
|
||||
} else {
|
||||
/* Log the missing content info. */
|
||||
const u32 version = m_validation_info.invalid_key.version;
|
||||
this->LogText("Validation failed with result: 0x%08x\n", m_validation_info.result);
|
||||
this->LogText("Missing content:\n- Program id: %016lx\n- Version: %d.%d.%d\n", m_validation_info.invalid_key.id, (version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf);
|
||||
|
||||
/* Log the missing content id. */
|
||||
this->LogText("- Content id: ");
|
||||
for (size_t i = 0; i < sizeof(NcmContentId); i++) {
|
||||
this->LogText("%02x", m_validation_info.invalid_content_id.c[i]);
|
||||
}
|
||||
this->LogText("\n");
|
||||
|
||||
/* Enable the back button and select it. */
|
||||
this->SetButtonEnabled(BackButtonId, true);
|
||||
this->SetButtonSelected(BackButtonId, true);
|
||||
}
|
||||
|
||||
/* Mark validation as being complete. */
|
||||
m_has_validated = true;
|
||||
}
|
||||
|
||||
void ValidateUpdateMenu::Update(u64 ns) {
|
||||
/* Perform validation if it hasn't been done already. */
|
||||
if (m_has_info && m_has_drawn && !m_has_validated) {
|
||||
this->ValidateUpdate();
|
||||
}
|
||||
|
||||
u64 k_down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
|
||||
/* Go back if B is pressed. */
|
||||
if (k_down & KEY_B) {
|
||||
ReturnToPreviousMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Take action if a button has been activated. */
|
||||
if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
|
||||
switch (activated_button->id) {
|
||||
case BackButtonId:
|
||||
ReturnToPreviousMenu();
|
||||
return;
|
||||
case ContinueButtonId:
|
||||
/* Don't continue if validation hasn't been done or has failed. */
|
||||
if (!m_has_validated || R_FAILED(m_validation_info.result)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_update_info.exfat_supported) {
|
||||
ChangeMenu(std::make_shared<ChooseExfatMenu>(g_current_menu));
|
||||
} else {
|
||||
g_use_exfat = false;
|
||||
ChangeMenu(std::make_shared<InstallUpdateMenu>(g_current_menu));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->UpdateButtons();
|
||||
}
|
||||
|
||||
void ValidateUpdateMenu::Draw(NVGcontext *vg, u64 ns) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
DrawWindow(vg, "Update information", x, y, WindowWidth, WindowHeight);
|
||||
DrawTextBackground(vg, x + HorizontalGap, y + TitleGap, WindowWidth - HorizontalGap * 2.0f, TextAreaHeight);
|
||||
DrawTextBlock(vg, m_log_buffer, x + HorizontalGap + TextHorizontalInset, y + TitleGap + TextVerticalInset, WindowWidth - (HorizontalGap + TextHorizontalInset) * 2.0f, TextAreaHeight - TextVerticalInset * 2.0f);
|
||||
|
||||
this->DrawButtons(vg, ns);
|
||||
m_has_drawn = true;
|
||||
}
|
||||
|
||||
ChooseExfatMenu::ChooseExfatMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
this->AddButton(Fat32ButtonId, "FAT32", x + ButtonHorizontalInset, y + TitleGap, ButtonWidth, ButtonHeight);
|
||||
this->AddButton(ExFatButtonId, "exFAT", x + ButtonHorizontalInset + ButtonWidth + ButtonHorizontalGap, y + TitleGap, ButtonWidth, ButtonHeight);
|
||||
this->SetButtonSelected(ExFatButtonId, true);
|
||||
}
|
||||
|
||||
void ChooseExfatMenu::Update(u64 ns) {
|
||||
u64 k_down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
|
||||
/* Go back if B is pressed. */
|
||||
if (k_down & KEY_B) {
|
||||
ReturnToPreviousMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Take action if a button has been activated. */
|
||||
if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
|
||||
switch (activated_button->id) {
|
||||
case Fat32ButtonId:
|
||||
g_use_exfat = false;
|
||||
break;
|
||||
case ExFatButtonId:
|
||||
g_use_exfat = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ChangeMenu(std::make_shared<InstallUpdateMenu>(g_current_menu));
|
||||
}
|
||||
|
||||
this->UpdateButtons();
|
||||
|
||||
/* Fallback on selecting the exfat button. */
|
||||
if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
|
||||
this->SetButtonSelected(ExFatButtonId, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseExfatMenu::Draw(NVGcontext *vg, u64 ns) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
DrawWindow(vg, "Select driver variant", x, y, WindowWidth, WindowHeight);
|
||||
this->DrawButtons(vg, ns);
|
||||
}
|
||||
|
||||
InstallUpdateMenu::InstallUpdateMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu), m_install_state(InstallState::NeedsDraw), m_progress_percent(0.0f) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
/* Add buttons. */
|
||||
this->AddButton(ShutdownButtonId, "Shutdown", x + HorizontalGap, y + WindowHeight - BottomGap - ButtonHeight, ButtonWidth, ButtonHeight);
|
||||
this->AddButton(RebootButtonId, "Reboot", x + HorizontalGap + ButtonWidth + ButtonHorizontalGap, y + WindowHeight - BottomGap - ButtonHeight, ButtonWidth, ButtonHeight);
|
||||
this->SetButtonEnabled(ShutdownButtonId, false);
|
||||
this->SetButtonEnabled(RebootButtonId, false);
|
||||
}
|
||||
|
||||
void InstallUpdateMenu::MarkForReboot() {
|
||||
this->SetButtonEnabled(ShutdownButtonId, true);
|
||||
this->SetButtonEnabled(RebootButtonId, true);
|
||||
this->SetButtonSelected(RebootButtonId, true);
|
||||
m_install_state = InstallState::AwaitingReboot;
|
||||
}
|
||||
|
||||
Result InstallUpdateMenu::TransitionUpdateState() {
|
||||
Result rc = 0;
|
||||
if (m_install_state == InstallState::NeedsSetup) {
|
||||
/* Setup the update. */
|
||||
if (R_FAILED(rc = amssuSetupUpdate(nullptr, UpdateTaskBufferSize, g_update_path, g_use_exfat))) {
|
||||
this->LogText("Failed to setup update.\nResult: 0x%08x\n", rc);
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Log setup completion. */
|
||||
this->LogText("Update setup complete.\n");
|
||||
m_install_state = InstallState::NeedsPrepare;
|
||||
} else if (m_install_state == InstallState::NeedsPrepare) {
|
||||
/* Request update preparation. */
|
||||
if (R_FAILED(rc = amssuRequestPrepareUpdate(&m_prepare_result))) {
|
||||
this->LogText("Failed to request update preparation.\nResult: 0x%08x\n", rc);
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Log awaiting prepare. */
|
||||
this->LogText("Preparing update...\n");
|
||||
m_install_state = InstallState::AwaitingPrepare;
|
||||
} else if (m_install_state == InstallState::AwaitingPrepare) {
|
||||
/* Check if preparation has a result. */
|
||||
if (R_FAILED(rc = asyncResultWait(&m_prepare_result, 0)) && rc != 0xea01) {
|
||||
this->LogText("Failed to check update preparation result.\nResult: 0x%08x\n", rc);
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
} else if (R_SUCCEEDED(rc)) {
|
||||
if (R_FAILED(rc = asyncResultGet(&m_prepare_result))) {
|
||||
this->LogText("Failed to prepare update.\nResult: 0x%08x\n", rc);
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the update has been prepared. */
|
||||
bool prepared;
|
||||
if (R_FAILED(rc = amssuHasPreparedUpdate(&prepared))) {
|
||||
this->LogText("Failed to check if update has been prepared.\nResult: 0x%08x\n", rc);
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Mark for application if preparation complete. */
|
||||
if (prepared) {
|
||||
this->LogText("Update preparation complete.\nApplying update...\n");
|
||||
m_install_state = InstallState::NeedsApply;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check update progress. */
|
||||
NsSystemUpdateProgress update_progress = {};
|
||||
if (R_FAILED(rc = amssuGetPrepareUpdateProgress(&update_progress))) {
|
||||
this->LogText("Failed to check update progress.\nResult: 0x%08x\n", rc);
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Update progress percent. */
|
||||
if (update_progress.total_size > 0.0f) {
|
||||
m_progress_percent = static_cast<float>(update_progress.current_size) / static_cast<float>(update_progress.total_size);
|
||||
} else {
|
||||
m_progress_percent = 0.0f;
|
||||
}
|
||||
} else if (m_install_state == InstallState::NeedsApply) {
|
||||
/* Apply the prepared update. */
|
||||
if (R_FAILED(rc = amssuApplyPreparedUpdate())) {
|
||||
this->LogText("Failed to apply update.\nResult: 0x%08x\n", rc);
|
||||
}
|
||||
|
||||
/* Log success. */
|
||||
this->LogText("Update applied successfully.\n");
|
||||
this->MarkForReboot();
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void InstallUpdateMenu::Update(u64 ns) {
|
||||
/* Transition to the next update state. */
|
||||
if (m_install_state != InstallState::NeedsDraw && m_install_state != InstallState::AwaitingReboot) {
|
||||
this->TransitionUpdateState();
|
||||
}
|
||||
|
||||
/* Take action if a button has been activated. */
|
||||
if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
|
||||
switch (activated_button->id) {
|
||||
case ShutdownButtonId:
|
||||
if (R_FAILED(appletRequestToShutdown())) {
|
||||
spsmShutdown(false);
|
||||
}
|
||||
break;
|
||||
case RebootButtonId:
|
||||
if (R_FAILED(appletRequestToReboot())) {
|
||||
spsmShutdown(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->UpdateButtons();
|
||||
}
|
||||
|
||||
void InstallUpdateMenu::Draw(NVGcontext *vg, u64 ns) {
|
||||
const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
|
||||
const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
|
||||
|
||||
DrawWindow(vg, "Installing update", x, y, WindowWidth, WindowHeight);
|
||||
DrawProgressText(vg, x + HorizontalGap, y + TitleGap, m_progress_percent);
|
||||
DrawProgressBar(vg, x + HorizontalGap, y + TitleGap + ProgressTextHeight, WindowWidth - HorizontalGap * 2.0f, ProgressBarHeight, m_progress_percent);
|
||||
DrawTextBackground(vg, x + HorizontalGap, y + TitleGap + ProgressTextHeight + ProgressBarHeight + VerticalGap, WindowWidth - HorizontalGap * 2.0f, TextAreaHeight);
|
||||
DrawTextBlock(vg, m_log_buffer, x + HorizontalGap + TextHorizontalInset, y + TitleGap + ProgressTextHeight + ProgressBarHeight + VerticalGap + TextVerticalInset, WindowWidth - (HorizontalGap + TextHorizontalInset) * 2.0f, TextAreaHeight - TextVerticalInset * 2.0f);
|
||||
|
||||
this->DrawButtons(vg, ns);
|
||||
|
||||
/* We have drawn now, allow setup to occur. */
|
||||
if (m_install_state == InstallState::NeedsDraw) {
|
||||
this->LogText("Beginning update setup...\n");
|
||||
m_install_state = InstallState::NeedsSetup;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeMenu(u32 screen_width, u32 screen_height) {
|
||||
/* Set the screen width and height. */
|
||||
g_screen_width = screen_width;
|
||||
g_screen_height = screen_height;
|
||||
|
||||
/* Change the current menu to the main menu. */
|
||||
g_current_menu = std::make_shared<MainMenu>();
|
||||
|
||||
/* Mark as initialized. */
|
||||
g_initialized = true;
|
||||
}
|
||||
|
||||
void UpdateMenu(u64 ns) {
|
||||
DBK_ABORT_UNLESS(g_initialized);
|
||||
DBK_ABORT_UNLESS(g_current_menu != nullptr);
|
||||
UpdateInput();
|
||||
g_current_menu->Update(ns);
|
||||
}
|
||||
|
||||
void RenderMenu(NVGcontext *vg, u64 ns) {
|
||||
DBK_ABORT_UNLESS(g_initialized);
|
||||
DBK_ABORT_UNLESS(g_current_menu != nullptr);
|
||||
|
||||
/* Draw background. */
|
||||
DrawBackground(vg, g_screen_width, g_screen_height);
|
||||
|
||||
/* Draw stars. */
|
||||
DrawStar(vg, 40.0f, 64.0f, 3.0f);
|
||||
DrawStar(vg, 110.0f, 300.0f, 3.0f);
|
||||
DrawStar(vg, 200.0f, 150.0f, 4.0f);
|
||||
DrawStar(vg, 370.0f, 280.0f, 3.0f);
|
||||
DrawStar(vg, 450.0f, 40.0f, 3.5f);
|
||||
DrawStar(vg, 710.0f, 90.0f, 3.0f);
|
||||
DrawStar(vg, 900.0f, 240.0f, 3.0f);
|
||||
DrawStar(vg, 970.0f, 64.0f, 4.0f);
|
||||
DrawStar(vg, 1160.0f, 160.0f, 3.5f);
|
||||
DrawStar(vg, 1210.0f, 350.0f, 3.0f);
|
||||
|
||||
g_current_menu->Draw(vg, ns);
|
||||
}
|
||||
|
||||
bool IsExitRequested() {
|
||||
return g_exit_requested;
|
||||
}
|
||||
|
||||
}
|
240
troposphere/daybreak/source/ui.hpp
Normal file
240
troposphere/daybreak/source/ui.hpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <nanovg.h>
|
||||
#include <switch.h>
|
||||
#include "ams_su.h"
|
||||
|
||||
namespace dbk {
|
||||
|
||||
struct Button {
|
||||
static constexpr u32 InvalidButtonId = -1;
|
||||
|
||||
u32 id;
|
||||
bool selected;
|
||||
bool enabled;
|
||||
char text[256];
|
||||
float x;
|
||||
float y;
|
||||
float w;
|
||||
float h;
|
||||
|
||||
inline bool IsPositionInBounds(float x, float y) {
|
||||
return x >= this->x && y >= this->y && x < (this->x + this->w) && y < (this->y + this->h);
|
||||
}
|
||||
};
|
||||
|
||||
enum class Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
class Menu {
|
||||
protected:
|
||||
static constexpr size_t MaxButtons = 32;
|
||||
static constexpr size_t LogBufferSize = 0x1000;
|
||||
protected:
|
||||
std::array<std::optional<Button>, MaxButtons> m_buttons;
|
||||
const std::shared_ptr<Menu> m_prev_menu;
|
||||
char m_log_buffer[LogBufferSize];
|
||||
protected:
|
||||
void AddButton(u32 id, const char *text, float x, float y, float w, float h);
|
||||
void SetButtonSelected(u32 id, bool selected);
|
||||
void DeselectAllButtons();
|
||||
void SetButtonEnabled(u32 id, bool enabled);
|
||||
|
||||
Button *GetButton(u32 id);
|
||||
Button *GetSelectedButton();
|
||||
Button *GetClosestButtonToSelection(Direction direction);
|
||||
Button *GetTouchedButton();
|
||||
Button *GetActivatedButton();
|
||||
|
||||
void UpdateButtons();
|
||||
void DrawButtons(NVGcontext *vg, u64 ns);
|
||||
|
||||
void LogText(const char *format, ...);
|
||||
public:
|
||||
Menu(std::shared_ptr<Menu> prev_menu) : m_buttons({}), m_prev_menu(prev_menu), m_log_buffer{} { /* ... */ }
|
||||
|
||||
std::shared_ptr<Menu> GetPrevMenu();
|
||||
virtual void Update(u64 ns) = 0;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) = 0;
|
||||
};
|
||||
|
||||
class MainMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 InstallButtonId = 0;
|
||||
static constexpr u32 ExitButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 400.0f;
|
||||
static constexpr float WindowHeight = 240.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float ButtonHorizontalPadding = 20.0f;
|
||||
static constexpr float ButtonHeight = 60.0f;
|
||||
static constexpr float ButtonVerticalGap = 10.0f;
|
||||
public:
|
||||
MainMenu();
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class FileMenu : public Menu {
|
||||
private:
|
||||
struct FileEntry {
|
||||
char name[FS_MAX_PATH];
|
||||
};
|
||||
private:
|
||||
static constexpr size_t MaxFileRows = 11;
|
||||
|
||||
static constexpr float WindowWidth = 1200.0f;
|
||||
static constexpr float WindowHeight = 680.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float TextBackgroundOffset = 20.0f;
|
||||
static constexpr float FileRowHeight = 40.0f;
|
||||
static constexpr float FileRowGap = 10.0f;
|
||||
static constexpr float FileRowHorizontalInset = 10.0f;
|
||||
static constexpr float FileListHeight = MaxFileRows * (FileRowHeight + FileRowGap);
|
||||
private:
|
||||
char m_root[FS_MAX_PATH];
|
||||
std::vector<FileEntry> m_file_entries;
|
||||
u32 m_current_index;
|
||||
float m_scroll_offset;
|
||||
float m_touch_start_scroll_offset;
|
||||
bool m_touch_finalize_selection;
|
||||
|
||||
Result PopulateFileEntries();
|
||||
bool IsSelectionVisible();
|
||||
void ScrollToSelection();
|
||||
bool IsEntryTouched(u32 i);
|
||||
void UpdateTouches();
|
||||
void FinalizeSelection();
|
||||
public:
|
||||
FileMenu(std::shared_ptr<Menu> prev_menu, const char *root);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class ValidateUpdateMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 BackButtonId = 0;
|
||||
static constexpr u32 ContinueButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 600.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float BottomGap = 20.0f;
|
||||
static constexpr float HorizontalGap = 20.0f;
|
||||
static constexpr float TextAreaHeight = 410.0f;
|
||||
static constexpr float TextHorizontalInset = 6.0f;
|
||||
static constexpr float TextVerticalInset = 6.0f;
|
||||
static constexpr float ButtonHeight = 60.0f;
|
||||
static constexpr float ButtonHorizontalGap = 10.0f;
|
||||
static constexpr float ButtonWidth = (WindowWidth - HorizontalGap * 2.0f) / 2.0f - ButtonHorizontalGap;
|
||||
private:
|
||||
AmsSuUpdateInformation m_update_info;
|
||||
AmsSuUpdateValidationInfo m_validation_info;
|
||||
bool m_has_drawn;
|
||||
bool m_has_info;
|
||||
bool m_has_validated;
|
||||
|
||||
Result GetUpdateInformation();
|
||||
void ValidateUpdate();
|
||||
public:
|
||||
ValidateUpdateMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class ChooseExfatMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 Fat32ButtonId = 0;
|
||||
static constexpr u32 ExFatButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 180.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float ButtonHeight = 60.0f;
|
||||
static constexpr float ButtonHorizontalInset = 20.0f;
|
||||
static constexpr float ButtonHorizontalGap = 10.0f;
|
||||
static constexpr float ButtonWidth = (WindowWidth - ButtonHorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap;
|
||||
public:
|
||||
ChooseExfatMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class InstallUpdateMenu : public Menu {
|
||||
private:
|
||||
enum class InstallState {
|
||||
NeedsDraw,
|
||||
NeedsSetup,
|
||||
NeedsPrepare,
|
||||
AwaitingPrepare,
|
||||
NeedsApply,
|
||||
AwaitingReboot,
|
||||
};
|
||||
private:
|
||||
static constexpr u32 ShutdownButtonId = 0;
|
||||
static constexpr u32 RebootButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 600.0f;
|
||||
static constexpr float TitleGap = 120.0f;
|
||||
static constexpr float BottomGap = 20.0f;
|
||||
static constexpr float HorizontalGap = 20.0f;
|
||||
static constexpr float ProgressTextHeight = 20.0f;
|
||||
static constexpr float ProgressBarHeight = 30.0f;
|
||||
static constexpr float VerticalGap = 10.0f;
|
||||
static constexpr float TextAreaHeight = 320.0f;
|
||||
static constexpr float TextHorizontalInset = 6.0f;
|
||||
static constexpr float TextVerticalInset = 6.0f;
|
||||
static constexpr float ButtonHeight = 60.0f;
|
||||
static constexpr float ButtonHorizontalGap = 10.0f;
|
||||
static constexpr float ButtonWidth = (WindowWidth - HorizontalGap * 2.0f) / 2.0f - ButtonHorizontalGap;
|
||||
|
||||
static constexpr size_t UpdateTaskBufferSize = 0x100000;
|
||||
private:
|
||||
InstallState m_install_state;
|
||||
AsyncResult m_prepare_result;
|
||||
float m_progress_percent;
|
||||
|
||||
void MarkForReboot();
|
||||
Result TransitionUpdateState();
|
||||
public:
|
||||
InstallUpdateMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
void InitializeMenu(u32 screen_width, u32 screen_height);
|
||||
void UpdateMenu(u64 ns);
|
||||
void RenderMenu(NVGcontext *vg, u64 ns);
|
||||
bool IsExitRequested();
|
||||
|
||||
}
|
192
troposphere/daybreak/source/ui_util.cpp
Normal file
192
troposphere/daybreak/source/ui_util.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ui_util.hpp"
|
||||
#include <cstdio>
|
||||
#include <math.h>
|
||||
|
||||
namespace dbk {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char *SwitchStandardFont = "switch-standard";
|
||||
constexpr float WindowCornerRadius = 20.0f;
|
||||
constexpr float TextAreaCornerRadius = 10.0f;
|
||||
constexpr float ButtonCornerRaidus = 3.0f;
|
||||
|
||||
NVGcolor GetSelectionRGB2(u64 ns) {
|
||||
/* Calculate the rgb values for the breathing colour effect. */
|
||||
const double t = static_cast<double>(ns) / 1'000'000'000.0d;
|
||||
const float d = -0.5 * cos(3.0f*t) + 0.5f;
|
||||
const int r2 = 83 + (float)(144 - 83) * (d * 0.7f + 0.3f);
|
||||
const int g2 = 71 + (float)(185 - 71) * (d * 0.7f + 0.3f);
|
||||
const int b2 = 185 + (float)(217 - 185) * (d * 0.7f + 0.3f);
|
||||
return nvgRGB(r2, g2, b2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DrawStar(NVGcontext *vg, float x, float y, float width) {
|
||||
nvgBeginPath(vg);
|
||||
nvgEllipse(vg, x, y, width, width * 3.0f);
|
||||
nvgEllipse(vg, x, y, width * 3.0f, width);
|
||||
nvgFillColor(vg, nvgRGB(65, 71, 115));
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void DrawBackground(NVGcontext *vg, float w, float h) {
|
||||
/* Draw the background gradient. */
|
||||
const NVGpaint bg_paint = nvgLinearGradient(vg, w / 2.0f, 0, w / 2.0f, h + 20.0f, nvgRGB(20, 24, 50), nvgRGB(46, 57, 127));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, 0, 0, w, h);
|
||||
nvgFillPaint(vg, bg_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void DrawWindow(NVGcontext *vg, const char *title, float x, float y, float w, float h) {
|
||||
/* Draw the window background. */
|
||||
const NVGpaint window_bg_paint = nvgLinearGradient(vg, x + w / 2.0f, y, x + w / 2.0f, y + h + h / 4.0f, nvgRGB(255, 255, 255), nvgRGB(188, 214, 234));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius);
|
||||
nvgFillPaint(vg, window_bg_paint);
|
||||
nvgFill(vg);
|
||||
|
||||
/* Draw the shadow surrounding the window. */
|
||||
NVGpaint shadowPaint = nvgBoxGradient(vg, x, y + 2, w, h, WindowCornerRadius * 2, 10, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, x - 10, y - 10, w + 20, h + 30);
|
||||
nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius);
|
||||
nvgPathWinding(vg, NVG_HOLE);
|
||||
nvgFillPaint(vg, shadowPaint);
|
||||
nvgFill(vg);
|
||||
|
||||
/* Setup the font. */
|
||||
nvgFontSize(vg, 32.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
|
||||
/* Draw the title. */
|
||||
const float tw = nvgTextBounds(vg, 0, 0, title, nullptr, nullptr);
|
||||
nvgText(vg, x + w * 0.5f - tw * 0.5f, y + 40.0f, title, nullptr);
|
||||
}
|
||||
|
||||
void DrawButton(NVGcontext *vg, const char *text, float x, float y, float w, float h, ButtonStyle style, u64 ns) {
|
||||
/* Fill the background if selected. */
|
||||
if (style == ButtonStyle::StandardSelected || style == ButtonStyle::FileSelectSelected) {
|
||||
NVGpaint bg_paint = nvgLinearGradient(vg, x, y + h / 2.0f, x + w, y + h / 2.0f, nvgRGB(83, 71, 185), GetSelectionRGB2(ns));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, ButtonCornerRaidus);
|
||||
nvgFillPaint(vg, bg_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
/* Draw the shadow surrounding the button. */
|
||||
if (style == ButtonStyle::Standard || style == ButtonStyle::StandardSelected || style == ButtonStyle::StandardDisabled || style == ButtonStyle::FileSelectSelected) {
|
||||
const unsigned char shadow_color = style == ButtonStyle::Standard ? 128 : 64;
|
||||
NVGpaint shadow_paint = nvgBoxGradient(vg, x, y, w, h, ButtonCornerRaidus, 5, nvgRGBA(0, 0, 0, shadow_color), nvgRGBA(0, 0, 0, 0));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, x - 10, y - 10, w + 20, h + 30);
|
||||
nvgRoundedRect(vg, x, y, w, h, ButtonCornerRaidus);
|
||||
nvgPathWinding(vg, NVG_HOLE);
|
||||
nvgFillPaint(vg, shadow_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
/* Setup the font. */
|
||||
nvgFontSize(vg, 20.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
|
||||
/* Set the text colour. */
|
||||
if (style == ButtonStyle::StandardSelected || style == ButtonStyle::FileSelectSelected) {
|
||||
nvgFillColor(vg, nvgRGB(255, 255, 255));
|
||||
} else {
|
||||
const unsigned char alpha = style == ButtonStyle::StandardDisabled ? 64 : 255;
|
||||
nvgFillColor(vg, nvgRGBA(0, 0, 0, alpha));
|
||||
}
|
||||
|
||||
/* Draw the button text. */
|
||||
const float tw = nvgTextBounds(vg, 0, 0, text, nullptr, nullptr);
|
||||
|
||||
if (style == ButtonStyle::Standard || style == ButtonStyle::StandardSelected || style == ButtonStyle::StandardDisabled) {
|
||||
nvgText(vg, x + w * 0.5f - tw * 0.5f, y + h * 0.5f, text, nullptr);
|
||||
} else {
|
||||
nvgText(vg, x + 10.0f, y + h * 0.5f, text, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTextBackground(NVGcontext *vg, float x, float y, float w, float h) {
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, TextAreaCornerRadius);
|
||||
nvgFillColor(vg, nvgRGBA(0, 0, 0, 16));
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void DrawProgressText(NVGcontext *vg, float x, float y, float progress) {
|
||||
char progress_text[32] = {};
|
||||
snprintf(progress_text, sizeof(progress_text)-1, "%d%% complete", static_cast<int>(progress * 100.0f));
|
||||
|
||||
nvgFontSize(vg, 24.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
nvgText(vg, x, y, progress_text, nullptr);
|
||||
}
|
||||
|
||||
void DrawProgressBar(NVGcontext *vg, float x, float y, float w, float h, float progress) {
|
||||
/* Draw the progress bar background. */
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius);
|
||||
nvgFillColor(vg, nvgRGBA(0, 0, 0, 128));
|
||||
nvgFill(vg);
|
||||
|
||||
/* Draw the progress bar fill. */
|
||||
if (progress > 0.0f) {
|
||||
NVGpaint progress_fill_paint = nvgLinearGradient(vg, x, y + 0.5f * h, x + w, y + 0.5f * h, nvgRGB(83, 71, 185), nvgRGB(144, 185, 217));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, WindowCornerRadius + (w - WindowCornerRadius) * progress, h, WindowCornerRadius);
|
||||
nvgFillPaint(vg, progress_fill_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTextBlock(NVGcontext *vg, const char *text, float x, float y, float w, float h) {
|
||||
/* Save state and scissor. */
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, x, y, w, h);
|
||||
|
||||
/* Configure the text. */
|
||||
nvgFontSize(vg, 18.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
|
||||
/* Determine the bounds of the text box. */
|
||||
float bounds[4];
|
||||
nvgTextBoxBounds(vg, 0, 0, w, text, nullptr, bounds);
|
||||
|
||||
/* Adjust the y to only show the last part of the text that fits. */
|
||||
float y_adjustment = 0.0f;
|
||||
if (bounds[3] > h) {
|
||||
y_adjustment = bounds[3] - h;
|
||||
}
|
||||
|
||||
/* Draw the text box and restore state. */
|
||||
nvgTextBox(vg, x, y - y_adjustment, w, text, nullptr);
|
||||
nvgRestore(vg);
|
||||
}
|
||||
|
||||
}
|
39
troposphere/daybreak/source/ui_util.hpp
Normal file
39
troposphere/daybreak/source/ui_util.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <nanovg.h>
|
||||
#include <switch.h>
|
||||
|
||||
namespace dbk {
|
||||
|
||||
enum class ButtonStyle {
|
||||
Standard,
|
||||
StandardSelected,
|
||||
StandardDisabled,
|
||||
FileSelect,
|
||||
FileSelectSelected,
|
||||
};
|
||||
|
||||
void DrawStar(NVGcontext *vg, float x, float y, float width);
|
||||
void DrawBackground(NVGcontext *vg, float w, float h);
|
||||
void DrawWindow(NVGcontext *vg, const char *title, float x, float y, float w, float h);
|
||||
void DrawButton(NVGcontext *vg, const char *text, float x, float y, float w, float h, ButtonStyle style, u64 ns);
|
||||
void DrawTextBackground(NVGcontext *vg, float x, float y, float w, float h);
|
||||
void DrawProgressText(NVGcontext *vg, float x, float y, float progress);
|
||||
void DrawProgressBar(NVGcontext *vg, float x, float y, float w, float h, float progress);
|
||||
void DrawTextBlock(NVGcontext *vg, const char *text, float x, float y, float w, float h);
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue