{
+ let lines = read_lines(filename)?;
+
+ let mut sections = vec![];
+ let mut preamble = vec![];
+ let mut cur_section = None;
+
+ let mut has_bom = false;
+
+ for line in lines {
+ let line = line.unwrap();
+
+ let line = if let Some(line) = line.strip_prefix('\u{feff}') {
+ has_bom = true;
+ line
+ } else {
+ &line
+ };
+
+ if let Some('[') = line.chars().next() {
+ if let Some(right_bracket) = line.find(']') {
+ if let Some(section) = cur_section.take() {
+ sections.push(section);
+ }
+
+ let name = &line[1..right_bracket];
+ cur_section = Some(Section {
+ name: name.to_owned(),
+ title_line: line.to_owned(), // preserves comment and bom
+ lines: vec![],
+ });
+ } else {
+ // Bad syntax
+ break;
+ }
+ } else if let Some(cur_section) = &mut cur_section {
+ cur_section.lines.push(line.to_owned());
+ } else {
+ preamble.push(line.to_owned());
+ }
+ }
+
+ if let Some(section) = cur_section.take() {
+ sections.push(section);
+ }
+
+ let ini = IniFile {
+ filename: PathBuf::from(filename),
+ preamble,
+ sections,
+ has_bom,
+ };
+ Ok(ini)
+ }
+
+ pub fn write(&self) -> io::Result<()> {
+ let file = std::fs::File::create(&self.filename)?;
+ let mut file = std::io::LineWriter::new(file);
+
+ // Write BOM
+ if self.has_bom {
+ file.write_all("\u{feff}".as_bytes())?;
+ }
+ for line in &self.preamble {
+ file.write_all(line.as_bytes())?;
+ file.write_all(b"\n")?;
+ }
+ for section in &self.sections {
+ file.write_all(section.title_line.as_bytes())?;
+ file.write_all(b"\n")?;
+ for line in §ion.lines {
+ file.write_all(line.as_bytes())?;
+ file.write_all(b"\n")?;
+ }
+ }
+
+ Ok(())
+ }
+
+ // Assumes alphabetical section order!
+ pub fn insert_section_if_missing(&mut self, section: &Section) -> bool {
+ // First, check if it's there.
+
+ for iter_section in &self.sections {
+ if iter_section.name == section.name {
+ return false;
+ }
+ }
+
+ // Then, find a suitable insertion spot
+ for (i, iter_section) in self.sections.iter_mut().enumerate() {
+ if iter_section.name > section.name {
+ println!("Inserting section {}", section.name);
+ self.sections.insert(i, section.clone());
+ return true;
+ }
+ }
+ // Reached the end for some reason? Add it.
+ self.sections.push(section.clone());
+ true
+ }
+
+ pub fn get_section_mut(&mut self, section_name: &str) -> Option<&mut Section> {
+ for section in &mut self.sections {
+ if section.name == section_name {
+ return Some(section);
+ }
+ }
+ None
+ }
+}
+
+// Grabbed from a sample, a fast line reader iterator.
+fn read_lines(filename: P) -> io::Result>>
+where
+ P: AsRef,
+{
+ let file = File::open(filename)?;
+ use std::io::BufRead;
+ Ok(io::BufReader::new(file).lines())
+}
diff --git a/Tools/langtool/src/main.rs b/Tools/langtool/src/main.rs
new file mode 100644
index 0000000000..0ab1bc29ee
--- /dev/null
+++ b/Tools/langtool/src/main.rs
@@ -0,0 +1,112 @@
+use std::io;
+
+mod section;
+
+mod inifile;
+use inifile::IniFile;
+
+use structopt::StructOpt;
+
+#[derive(StructOpt, Debug)]
+struct Opt {
+ #[structopt(subcommand)]
+ cmd: Command,
+}
+
+#[derive(StructOpt, Debug)]
+enum Command {
+ CopyMissingLines {},
+ CommentUnknownLines {},
+ RemoveUnknownLines {},
+}
+
+fn copy_missing_lines(reference_ini: &IniFile, target_ini: &mut IniFile) -> io::Result<()> {
+ // Insert any missing full sections.
+ for reference_section in &reference_ini.sections {
+ if !target_ini.insert_section_if_missing(reference_section) {
+ if let Some(target_section) = target_ini.get_section_mut(&reference_section.name) {
+ for line in &reference_section.lines {
+ target_section.insert_line_if_missing(line);
+ }
+
+ //target_section.remove_lines_if_not_in(reference_section);
+ target_section.comment_out_lines_if_not_in(reference_section);
+ }
+ }
+ }
+ Ok(())
+}
+
+fn deal_with_unknown_lines(
+ reference_ini: &IniFile,
+ target_ini: &mut IniFile,
+ remove: bool,
+) -> io::Result<()> {
+ // Insert any missing full sections.
+ for reference_section in &reference_ini.sections {
+ if let Some(target_section) = target_ini.get_section_mut(&reference_section.name) {
+ if remove {
+ target_section.remove_lines_if_not_in(reference_section);
+ } else {
+ target_section.comment_out_lines_if_not_in(reference_section);
+ }
+ }
+ }
+ Ok(())
+}
+
+fn main() {
+ let opt = Opt::from_args();
+
+ // TODO: Grab extra arguments from opt somehow.
+ let args: Vec = vec![]; //std::env::args().skip(1).collect();
+ let mut filenames = args;
+
+ let root = "../../assets/lang";
+ let reference_file = "en_US.ini";
+
+ let reference_ini = IniFile::parse(&format!("{}/{}", root, reference_file)).unwrap();
+
+ if filenames.is_empty() {
+ // Grab them all.
+ for path in std::fs::read_dir(root).unwrap() {
+ let path = path.unwrap();
+ if path.file_name() == reference_file {
+ continue;
+ }
+ let filename = path.file_name();
+ let filename = filename.to_string_lossy();
+ if !filename.ends_with(".ini") {
+ continue;
+ }
+ filenames.push(path.file_name().to_string_lossy().to_string());
+ }
+ }
+
+ for filename in filenames {
+ if filename == "langtool" {
+ // Get this from cargo run for some reason.
+ continue;
+ }
+ let target_ini_filename = format!("{}/{}", root, filename);
+ println!("Langtool processing {}", target_ini_filename);
+
+ let mut target_ini = IniFile::parse(&target_ini_filename).unwrap();
+
+ match opt.cmd {
+ Command::CopyMissingLines {} => {
+ copy_missing_lines(&reference_ini, &mut target_ini).unwrap();
+ }
+ Command::CommentUnknownLines {} => {
+ deal_with_unknown_lines(&reference_ini, &mut target_ini, false).unwrap();
+ }
+ Command::RemoveUnknownLines {} => {
+ deal_with_unknown_lines(&reference_ini, &mut target_ini, true).unwrap();
+ }
+ }
+
+ target_ini.write().unwrap();
+ }
+
+ // println!("{:#?}", target_ini);
+}
diff --git a/Tools/langtool/src/section.rs b/Tools/langtool/src/section.rs
new file mode 100644
index 0000000000..1812cd006e
--- /dev/null
+++ b/Tools/langtool/src/section.rs
@@ -0,0 +1,104 @@
+// Super simplified ini file processor.
+// Doesn't even bother with understanding comments.
+// Just understands section headings and
+// keys and values, split by ' = '.
+
+#[derive(Debug, Clone)]
+pub struct Section {
+ pub name: String,
+ pub title_line: String,
+ pub lines: Vec,
+}
+
+impl Section {
+ pub fn insert_line_if_missing(&mut self, line: &str) -> bool {
+ let prefix = if let Some(pos) = line.find(" =") {
+ &line[0..pos + 2]
+ } else {
+ return false;
+ };
+
+ // Ignore comments when copying lines.
+ if prefix.starts_with('#') {
+ return false;
+ }
+ // Need to decide a policy for these.
+ if prefix.starts_with("translators") {
+ return false;
+ }
+ let prefix = prefix.to_owned();
+
+ for iter_line in &self.lines {
+ if iter_line.starts_with(&prefix) {
+ // Already have it
+ return false;
+ }
+ }
+
+ // Now try to insert it at an alphabetic-ish location.
+ let prefix = prefix.to_ascii_lowercase();
+
+ // Then, find a suitable insertion spot
+ for (i, iter_line) in self.lines.iter().enumerate() {
+ if iter_line.to_ascii_lowercase() > prefix {
+ println!("Inserting line {} into {}", line, self.name);
+ self.lines.insert(i, line.to_owned());
+ return true;
+ }
+ }
+
+ for i in (0..self.lines.len()).rev() {
+ if self.lines[i].is_empty() {
+ continue;
+ }
+ println!("Inserting line {} into {}", line, self.name);
+ self.lines.insert(i + 1, line.to_owned());
+ return true;
+ }
+
+ println!("failed to insert {}", line);
+ true
+ }
+
+ pub fn comment_out_lines_if_not_in(&mut self, other: &Section) {
+ // Brute force (O(n^2)). Bad but not a problem.
+
+ for line in &mut self.lines {
+ let prefix = if let Some(pos) = line.find(" =") {
+ &line[0..pos + 2]
+ } else {
+ // Keep non-key lines.
+ continue;
+ };
+ if prefix.starts_with("Font") || prefix.starts_with('#') {
+ continue;
+ }
+ if !other.lines.iter().any(|line| line.starts_with(prefix)) {
+ println!("Commenting out: {}", line);
+ // Comment out the line.
+ *line = "#".to_owned() + line;
+ }
+ }
+ }
+
+ pub fn remove_lines_if_not_in(&mut self, other: &Section) {
+ // Brute force (O(n^2)). Bad but not a problem.
+
+ self.lines.retain(|line| {
+ let prefix = if let Some(pos) = line.find(" =") {
+ &line[0..pos + 2]
+ } else {
+ // Keep non-key lines.
+ return true;
+ };
+ if prefix.starts_with("Font") || prefix.starts_with('#') {
+ return true;
+ }
+ if !other.lines.iter().any(|line| line.starts_with(prefix)) {
+ false
+ } else {
+ true
+ }
+ });
+ }
+}
diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp
index 82d7bf586d..ff83abcfc5 100644
--- a/UI/BackgroundAudio.cpp
+++ b/UI/BackgroundAudio.cpp
@@ -344,7 +344,10 @@ int BackgroundAudio::Play() {
}
double now = time_now_d();
- int sz = lastPlaybackTime_ <= 0.0 ? 44100 / 60 : (int)((now - lastPlaybackTime_) * 44100);
+ int sz = 44100 / 60;
+ if (lastPlaybackTime_ > 0.0 && lastPlaybackTime_ <= now) {
+ sz = (int)((now - lastPlaybackTime_) * 44100);
+ }
sz = std::min(BUFSIZE / 2, sz);
if (at3Reader_) {
if (at3Reader_->Read(buffer, sz)) {
diff --git a/UI/ComboKeyMappingScreen.cpp b/UI/ComboKeyMappingScreen.cpp
index 4754c33bd8..931c0b0d9b 100644
--- a/UI/ComboKeyMappingScreen.cpp
+++ b/UI/ComboKeyMappingScreen.cpp
@@ -32,6 +32,62 @@
#include "UI/ComboKeyMappingScreen.h"
+class ButtonShapeScreen : public PopupScreen {
+public:
+ ButtonShapeScreen(std::string title, int *setting) : PopupScreen(title), setting_(setting) {}
+
+ void CreatePopupContents(UI::ViewGroup *parent) override {
+ using namespace UI;
+ using namespace CustomKey;
+
+ ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
+ LinearLayout *items = new LinearLayoutList(ORIENT_VERTICAL);
+
+ for (int i = 0; i < ARRAY_SIZE(comboKeyShapes); ++i) {
+ Choice *c = items->Add(new Choice(ImageID(comboKeyShapes[i].l), 0.6f, comboKeyShapes[i].r*PI/180, comboKeyShapes[i].f));
+ c->OnClick.Add([=](UI::EventParams &e) {
+ *setting_ = i;
+ TriggerFinish(DR_OK);
+ return UI::EVENT_DONE;
+ });
+ }
+
+ scroll->Add(items);
+ parent->Add(scroll);
+ }
+
+private:
+ int *setting_;
+};
+
+class ButtonIconScreen : public PopupScreen {
+public:
+ ButtonIconScreen(std::string title, int *setting) : PopupScreen(title), setting_(setting) {}
+
+ void CreatePopupContents(UI::ViewGroup *parent) override {
+ using namespace UI;
+ using namespace CustomKey;
+
+ ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
+ LinearLayout *items = new LinearLayoutList(ORIENT_VERTICAL);
+
+ for (int i = 0; i < ARRAY_SIZE(comboKeyImages); ++i) {
+ Choice *c = items->Add(new Choice(ImageID(comboKeyImages[i].i), 1.0f, comboKeyImages[i].r*PI/180));
+ c->OnClick.Add([=](UI::EventParams &e) {
+ *setting_ = i;
+ TriggerFinish(DR_OK);
+ return UI::EVENT_DONE;
+ });
+ }
+
+ scroll->Add(items);
+ parent->Add(scroll);
+ }
+
+private:
+ int *setting_;
+};
+
class ButtonPreview : public UI::View {
public:
ButtonPreview(ImageID bgImg, ImageID img, float rotationIcon, bool flipShape, float rotationShape, int x, int y)
@@ -152,23 +208,30 @@ void ComboKeyScreen::CreateViews() {
LinearLayout *vertLayout = new LinearLayout(ORIENT_VERTICAL);
rightScroll_->Add(vertLayout);
- vertLayout->Add(new ItemHeader(co->T("Button Style")));
+ vertLayout->Add(new ItemHeader(co->T("Button style")));
vertLayout->Add(new CheckBox(show, co->T("Visible")));
- // All icon and name are defined in GamepadEmu.h
- static const char *imageNames[ARRAY_SIZE(comboKeyImages)];
- for (int i = 0; i < ARRAY_SIZE(imageNames); ++i) {
- imageNames[i] = comboKeyImages[i].n;
- }
- PopupMultiChoice *icon = vertLayout->Add(new PopupMultiChoice(&(cfg->image), co->T("Icon"), imageNames, 0, ARRAY_SIZE(imageNames), mc->GetName(), screenManager()));
- icon->OnChoice.Handle(this, &ComboKeyScreen::onCombo);
-
- // All shape and name are defined in GamepadEmu.h
- static const char *shapeNames[ARRAY_SIZE(comboKeyShapes)];
- for (int i = 0; i < ARRAY_SIZE(shapeNames); ++i) {
- shapeNames[i] = comboKeyShapes[i].n;
- }
- vertLayout->Add(new PopupMultiChoice(&(cfg->shape), co->T("Shape"), shapeNames, 0, ARRAY_SIZE(shapeNames), mc->GetName(), screenManager()))->OnChoice.Handle(this, &ComboKeyScreen::onCombo);
+ Choice *icon = vertLayout->Add(new Choice(co->T("Icon")));
+ icon->SetIcon(ImageID(comboKeyImages[cfg->image].i), 1.0f, comboKeyImages[cfg->image].r*PI/180); // Set right icon on the choice
+ icon->OnClick.Add([=](UI::EventParams &e) {
+ auto iconScreen = new ButtonIconScreen(co->T("Icon"), &(cfg->image));
+ if (e.v)
+ iconScreen->SetPopupOrigin(e.v);
+
+ screenManager()->push(iconScreen);
+ return UI::EVENT_DONE;
+ });
+
+ Choice *shape = vertLayout->Add(new Choice(co->T("Shape")));
+ shape->SetIcon(ImageID(comboKeyShapes[cfg->shape].l), 0.6f, comboKeyShapes[cfg->shape].r*PI/180, comboKeyShapes[cfg->shape].f); // Set right icon on the choice
+ shape->OnClick.Add([=](UI::EventParams &e) {
+ auto shape = new ButtonShapeScreen(co->T("Shape"), &(cfg->shape));
+ if (e.v)
+ shape->SetPopupOrigin(e.v);
+
+ screenManager()->push(shape);
+ return UI::EVENT_DONE;
+ });
vertLayout->Add(new ItemHeader(co->T("Button Binding")));
vertLayout->Add(new CheckBox(&(cfg->toggle), co->T("Toggle mode")));
@@ -249,6 +312,11 @@ void ComboKeyScreen::saveArray() {
}
}
+void ComboKeyScreen::dialogFinished(const Screen *dialog, DialogResult result) {
+ saveArray();
+ RecreateViews();
+}
+
void ComboKeyScreen::onFinish(DialogResult result) {
saveArray();
g_Config.Save("ComboKeyScreen::onFinish");
@@ -258,9 +326,3 @@ UI::EventReturn ComboKeyScreen::ChoiceEventHandler::onChoiceClick(UI::EventParam
checkbox_->Toggle();
return UI::EVENT_DONE;
};
-
-UI::EventReturn ComboKeyScreen::onCombo(UI::EventParams &e) {
- saveArray();
- CreateViews();
- return UI::EVENT_DONE;
-}
diff --git a/UI/ComboKeyMappingScreen.h b/UI/ComboKeyMappingScreen.h
index aa4fd71a06..4ee477444d 100644
--- a/UI/ComboKeyMappingScreen.h
+++ b/UI/ComboKeyMappingScreen.h
@@ -32,7 +32,10 @@ public:
void CreateViews() override;
void onFinish(DialogResult result) override;
- UI::EventReturn onCombo(UI::EventParams &e);
+
+protected:
+ void dialogFinished(const Screen *dialog, DialogResult result) override;
+
private:
void saveArray();
diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp
index e29be742a5..94eef4dcd9 100644
--- a/UI/ControlMappingScreen.cpp
+++ b/UI/ControlMappingScreen.cpp
@@ -36,6 +36,7 @@
#include "Common/StringUtils.h"
#include "Common/System/Display.h"
#include "Common/System/System.h"
+#include "Common/TimeUtil.h"
#include "Core/KeyMap.h"
#include "Core/Host.h"
#include "Core/HLE/sceCtrl.h"
@@ -44,6 +45,10 @@
#include "UI/ControlMappingScreen.h"
#include "UI/GameSettingsScreen.h"
+#if PPSSPP_PLATFORM(ANDROID)
+#include "android/jni/app-android.h"
+#endif
+
class SingleControlMapper : public UI::LinearLayout {
public:
SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams = nullptr);
@@ -99,7 +104,7 @@ void SingleControlMapper::Refresh() {
using namespace UI;
- float itemH = 45;
+ float itemH = 55.0f;
float leftColumnWidth = 200;
float rightColumnWidth = 250; // TODO: Should be flexible somehow. Maybe we need to implement Measure.
@@ -250,7 +255,6 @@ void ControlMappingScreen::CreateViews() {
rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));
rightScroll_->SetTag("ControlMapping");
- rightScroll_->SetScrollToTop(false);
LinearLayout *rightColumn = new LinearLayoutList(ORIENT_VERTICAL);
rightScroll_->Add(rightColumn);
@@ -327,7 +331,7 @@ void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {
}
bool KeyMappingNewKeyDialog::key(const KeyInput &key) {
- if (mapped_)
+ if (mapped_ || time_now_d() < delayUntil_)
return false;
if (key.flags & KEY_DOWN) {
if (key.keyCode == NKCODE_EXT_MOUSEBUTTON_1) {
@@ -343,6 +347,10 @@ bool KeyMappingNewKeyDialog::key(const KeyInput &key) {
return true;
}
+void KeyMappingNewKeyDialog::SetDelay(float t) {
+ delayUntil_ = time_now_d() + t;
+}
+
void KeyMappingNewMouseKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {
using namespace UI;
@@ -393,7 +401,7 @@ static bool IgnoreAxisForMapping(int axis) {
bool KeyMappingNewKeyDialog::axis(const AxisInput &axis) {
- if (mapped_)
+ if (mapped_ || time_now_d() < delayUntil_)
return false;
if (IgnoreAxisForMapping(axis.axisId))
return false;
@@ -479,7 +487,8 @@ void JoystickHistoryView::Draw(UIContext &dc) {
}
float minRadius = std::min(bounds_.w, bounds_.h) * 0.5f - image->w;
dc.Begin();
- dc.DrawTextShadow(title_.c_str(), bounds_.centerX(), bounds_.centerY() + minRadius + 5.0, 0xFFFFFFFF, ALIGN_TOP | ALIGN_HCENTER);
+ Bounds textBounds(bounds_.x, bounds_.centerY() + minRadius + 5.0, bounds_.w, bounds_.h/2 - minRadius - 5.0);
+ dc.DrawTextShadowRect(title_.c_str(), textBounds, 0xFFFFFFFF, ALIGN_TOP | ALIGN_HCENTER | FLAG_WRAP_TEXT);
dc.Flush();
dc.BeginNoTex();
dc.Draw()->RectOutline(bounds_.centerX() - minRadius, bounds_.centerY() - minRadius, minRadius * 2.0f, minRadius * 2.0f, 0x80FFFFFF);
@@ -800,7 +809,7 @@ void TouchTestScreen::render() {
ui_context->Begin();
- char buffer[1024];
+ char buffer[2048];
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
if (touches_[i].id != -1) {
ui_context->Draw()->Circle(touches_[i].x, touches_[i].y, 100.0, 3.0, 80, 0.0f, 0xFFFFFFFF, 1.0);
@@ -809,15 +818,19 @@ void TouchTestScreen::render() {
}
}
+ char extra_debug[2048]{};
+
+#if PPSSPP_PLATFORM(ANDROID)
+ truncate_cpy(extra_debug, Android_GetInputDeviceDebugString().c_str());
+#endif
+
snprintf(buffer, sizeof(buffer),
#if PPSSPP_PLATFORM(ANDROID)
"display_res: %dx%d\n"
#endif
- "dp_res: %dx%d\n"
- "pixel_res: %dx%d\n"
- "g_dpi: %f\n"
- "g_dpi_scale: %0.3fx%0.3f\n"
- "g_dpi_scale_real: %0.3fx%0.3f\n",
+ "dp_res: %dx%d pixel_res: %dx%d\n"
+ "g_dpi: %0.3f g_dpi_scale: %0.3fx%0.3f\n"
+ "g_dpi_scale_real: %0.3fx%0.3f\n%s",
#if PPSSPP_PLATFORM(ANDROID)
display_xres, display_yres,
#endif
@@ -825,7 +838,10 @@ void TouchTestScreen::render() {
pixel_xres, pixel_yres,
g_dpi,
g_dpi_scale_x, g_dpi_scale_y,
- g_dpi_scale_real_x, g_dpi_scale_real_y);
+ g_dpi_scale_real_x, g_dpi_scale_real_y, extra_debug);
+
+ // On Android, also add joystick debug data.
+
ui_context->DrawTextShadow(buffer, bounds.centerX(), bounds.y + 20.0f, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ui_context->Flush();
@@ -1144,14 +1160,14 @@ void VisualMappingScreen::resized() {
UI::EventReturn VisualMappingScreen::OnMapButton(UI::EventParams &e) {
nextKey_ = e.a;
- MapNext();
+ MapNext(false);
return UI::EVENT_DONE;
}
UI::EventReturn VisualMappingScreen::OnBindAll(UI::EventParams &e) {
bindAll_ = 0;
nextKey_ = bindAllOrder[bindAll_];
- MapNext();
+ MapNext(false);
return UI::EVENT_DONE;
}
@@ -1184,7 +1200,7 @@ void VisualMappingScreen::HandleKeyMapping(KeyDef key) {
void VisualMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {
if (result == DR_YES && nextKey_ != 0) {
- MapNext();
+ MapNext(true);
} else {
// This means they canceled.
if (nextKey_ != 0)
@@ -1195,7 +1211,7 @@ void VisualMappingScreen::dialogFinished(const Screen *dialog, DialogResult resu
}
}
-void VisualMappingScreen::MapNext() {
+void VisualMappingScreen::MapNext(bool successive) {
auto km = GetI18NCategory("KeyMapping");
if (nextKey_ == VIRTKEY_AXIS_Y_MIN || nextKey_ == VIRTKEY_AXIS_X_MIN || nextKey_ == VIRTKEY_AXIS_X_MAX) {
@@ -1207,5 +1223,6 @@ void VisualMappingScreen::MapNext() {
Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();
dialog->SetPopupOffset(psp_->GetPopupOffset() * bounds.h);
+ dialog->SetDelay(successive ? 0.5f : 0.1f);
screenManager()->push(dialog);
}
diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h
index 393fb1f18c..add89bfdfa 100644
--- a/UI/ControlMappingScreen.h
+++ b/UI/ControlMappingScreen.h
@@ -57,13 +57,15 @@ private:
class KeyMappingNewKeyDialog : public PopupScreen {
public:
explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n)
- : PopupScreen(i18n->T("Map Key"), "Cancel", ""), callback_(callback), mapped_(false) {
+ : PopupScreen(i18n->T("Map Key"), "Cancel", ""), callback_(callback) {
pspBtn_ = btn;
}
virtual bool key(const KeyInput &key) override;
virtual bool axis(const AxisInput &axis) override;
+ void SetDelay(float t);
+
protected:
void CreatePopupContents(UI::ViewGroup *parent) override;
@@ -74,7 +76,8 @@ protected:
private:
int pspBtn_;
std::function callback_;
- bool mapped_; // Prevent double registrations
+ bool mapped_ = false; // Prevent double registrations
+ double delayUntil_ = 0.0f;
};
class KeyMappingNewMouseKeyDialog : public PopupScreen {
@@ -178,7 +181,7 @@ private:
UI::EventReturn OnMapButton(UI::EventParams &e);
UI::EventReturn OnBindAll(UI::EventParams &e);
void HandleKeyMapping(KeyDef key);
- void MapNext();
+ void MapNext(bool successive);
MockPSP *psp_ = nullptr;
int nextKey_ = 0;
diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp
index 57e252483e..3ec6b8139c 100644
--- a/UI/CwCheatScreen.cpp
+++ b/UI/CwCheatScreen.cpp
@@ -96,8 +96,7 @@ void CwCheatScreen::CreateViews() {
rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 0.5f));
rightScroll_->SetTag("CwCheats");
- rightScroll_->SetScrollToTop(false);
- rightScroll_->ScrollTo(g_Config.fCwCheatScrollPosition);
+ rightScroll_->RememberPosition(&g_Config.fCwCheatScrollPosition);
LinearLayout *rightColumn = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(200, FILL_PARENT, actionMenuMargins));
rightScroll_->Add(rightColumn);
@@ -140,7 +139,6 @@ void CwCheatScreen::onFinish(DialogResult result) {
if (MIPSComp::jit) {
MIPSComp::jit->ClearCache();
}
- g_Config.fCwCheatScrollPosition = rightScroll_->GetScrollPosition();
}
UI::EventReturn CwCheatScreen::OnEnableAll(UI::EventParams ¶ms) {
diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp
index 78695e8efd..c8d5fae07b 100644
--- a/UI/EmuScreen.cpp
+++ b/UI/EmuScreen.cpp
@@ -110,7 +110,7 @@ static void __EmuScreenVblank()
if (frameStep_ && lastNumFlips != gpuStats.numFlips)
{
frameStep_ = false;
- Core_EnableStepping(true);
+ Core_EnableStepping(true, "ui.frameAdvance", 0);
lastNumFlips = gpuStats.numFlips;
}
#ifndef MOBILE_DEVICE
@@ -185,6 +185,10 @@ EmuScreen::EmuScreen(const Path &filename)
OnDevMenu.Handle(this, &EmuScreen::OnDevTools);
OnChatMenu.Handle(this, &EmuScreen::OnChat);
+
+ // Usually, we don't want focus movement enabled on this screen, so disable on start.
+ // Only if you open chat or dev tools do we want it to start working.
+ UI::EnableFocusMovement(false);
}
bool EmuScreen::bootAllowStorage(const Path &filename) {
@@ -607,7 +611,7 @@ void EmuScreen::onVKeyDown(int virtualKeyCode) {
}
else if (!frameStep_)
{
- Core_EnableStepping(true);
+ Core_EnableStepping(true, "ui.frameAdvance", 0);
}
break;
@@ -736,15 +740,15 @@ bool EmuScreen::key(const KeyInput &key) {
Core_NotifyActivity();
if (UI::IsFocusMovementEnabled()) {
- if ((key.flags & KEY_DOWN) != 0 && UI::IsEscapeKey(key)) {
+ if (UIScreen::key(key)) {
+ return true;
+ } else if ((key.flags & KEY_DOWN) != 0 && UI::IsEscapeKey(key)) {
if (chatMenu_)
chatMenu_->Close();
if (chatButton_)
chatButton_->SetVisibility(UI::V_VISIBLE);
UI::EnableFocusMovement(false);
return true;
- } else if (UIScreen::key(key)) {
- return true;
}
}
@@ -834,31 +838,31 @@ void EmuScreen::CreateViews() {
AnchorLayoutParams *layoutParams = nullptr;
switch (g_Config.iChatButtonPosition) {
case 0:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, 80, NONE, NONE, 50, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, NONE, NONE, 50, true);
break;
case 1:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, bounds.centerX(), NONE, NONE, 50, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), NONE, NONE, 50, true);
break;
case 2:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, NONE, NONE, 80, 50, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, NONE, 80, 50, true);
break;
case 3:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, 80, 50, NONE, NONE, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, 50, NONE, NONE, true);
break;
case 4:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, bounds.centerX(), 50, NONE, NONE, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), 50, NONE, NONE, true);
break;
case 5:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, NONE, 50, 80, NONE, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, 50, 80, NONE, true);
break;
case 6:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, 80, bounds.centerY(), NONE, NONE, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, bounds.centerY(), NONE, NONE, true);
break;
case 7:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, NONE, bounds.centerY(), 80, NONE, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, bounds.centerY(), 80, NONE, true);
break;
default:
- layoutParams = new AnchorLayoutParams(130, WRAP_CONTENT, 80, NONE, NONE, 50, true);
+ layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, NONE, NONE, 50, true);
break;
}
@@ -1150,6 +1154,8 @@ static void DrawCrashDump(UIContext *ctx) {
sysName.c_str(), sysVersion, GetCompilerABI()
);
+ if (ctx->Draw()->GetFontAtlas()->getFont(ubuntu24))
+ ctx->BindFontTexture();
ctx->Draw()->SetFontScale(.7f, .7f);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 140;
@@ -1201,6 +1207,8 @@ BREAK
CheatsInEffect() ? "Y" : "N", HLEPlugins::HasEnabled() ? "Y" : "N");
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
+ ctx->Flush();
+ ctx->RebindTexture();
}
static void DrawAudioDebugStats(DrawBuffer *draw2d, const Bounds &bounds) {
diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp
index 9cb05ca450..2749fa98b3 100644
--- a/UI/GameScreen.cpp
+++ b/UI/GameScreen.cpp
@@ -168,7 +168,7 @@ void GameScreen::CreateViews() {
if (isRecentGame(gamePath_)) {
rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Remove From Recent"))))->OnClick.Handle(this, &GameScreen::OnRemoveFromRecent);
}
-#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
+#if (defined(USING_QT_UI) || PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)) && !PPSSPP_PLATFORM(UWP)
rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Show In Folder"))))->OnClick.Handle(this, &GameScreen::OnShowInFolder);
#endif
if (g_Config.bEnableCheats) {
diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp
index 37d5102c55..a049616f67 100644
--- a/UI/GameSettingsScreen.cpp
+++ b/UI/GameSettingsScreen.cpp
@@ -82,6 +82,7 @@
#if PPSSPP_PLATFORM(ANDROID)
#include "android/jni/AndroidAudio.h"
+#include "android/jni/AndroidContentURI.h"
extern AndroidAudioState *g_audioState;
@@ -163,8 +164,33 @@ static std::string *GPUDeviceNameSetting() {
return nullptr;
}
+bool PathToVisualUsbPath(Path path, std::string &outPath) {
+ switch (path.Type()) {
+ case PathType::NATIVE:
+ if (path.StartsWith(g_Config.memStickDirectory)) {
+ return g_Config.memStickDirectory.ComputePathTo(path, outPath);
+ }
+ break;
+ case PathType::CONTENT_URI:
+#if PPSSPP_PLATFORM(ANDROID)
+ {
+ // Try to parse something sensible out of the content URI.
+ AndroidContentURI uri(path.ToString());
+ outPath = uri.RootPath();
+ if (startsWith(outPath, "primary:")) {
+ outPath = "/" + outPath.substr(8);
+ }
+ return true;
+ }
+#endif
+ default:
+ break;
+ }
+ return false;
+}
+
void GameSettingsScreen::CreateViews() {
- ReloadAllPostShaderInfo();
+ ReloadAllPostShaderInfo(screenManager()->getDrawContext());
if (editThenRestore_) {
std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0);
@@ -186,6 +212,7 @@ void GameSettingsScreen::CreateViews() {
auto co = GetI18NCategory("Controls");
auto a = GetI18NCategory("Audio");
auto sa = GetI18NCategory("Savedata");
+ auto se = GetI18NCategory("Search");
auto sy = GetI18NCategory("System");
auto n = GetI18NCategory("Networking");
auto ms = GetI18NCategory("MainSettings");
@@ -195,20 +222,21 @@ void GameSettingsScreen::CreateViews() {
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
- TabHolder *tabHolder;
if (vertical) {
LinearLayout *verticalLayout = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT));
- tabHolder = new TabHolder(ORIENT_HORIZONTAL, 200, new LinearLayoutParams(1.0f));
- verticalLayout->Add(tabHolder);
+ tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, new LinearLayoutParams(1.0f));
+ verticalLayout->Add(tabHolder_);
verticalLayout->Add(new Choice(di->T("Back"), "", false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(0))))->OnClick.Handle(this, &UIScreen::OnBack);
root_->Add(verticalLayout);
} else {
- tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
- root_->Add(tabHolder);
+ tabHolder_ = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
+ root_->Add(tabHolder_);
AddStandardBack(root_);
}
- tabHolder->SetTag("GameSettings");
- root_->SetDefaultFocusView(tabHolder);
+ tabHolder_->SetTag("GameSettings");
+ root_->SetDefaultFocusView(tabHolder_);
+ settingTabContents_.clear();
+ settingTabFilterNotices_.clear();
float leftSide = 40.0f;
if (!vertical) {
@@ -226,12 +254,7 @@ void GameSettingsScreen::CreateViews() {
// TODO: These currently point to global settings, not game specific ones.
// Graphics
- ViewGroup *graphicsSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
- graphicsSettingsScroll->SetTag("GameSettingsGraphics");
- LinearLayout *graphicsSettings = new LinearLayoutList(ORIENT_VERTICAL);
- graphicsSettings->SetSpacing(0);
- graphicsSettingsScroll->Add(graphicsSettings);
- tabHolder->AddTab(ms->T("Graphics"), graphicsSettingsScroll);
+ LinearLayout *graphicsSettings = AddTab("GameSettingsGraphics", ms->T("Graphics"));
if (!g_Config.bSimpleUI) {
graphicsSettings->Add(new ItemHeader(gr->T("Rendering Mode")));
@@ -472,11 +495,15 @@ void GameSettingsScreen::CreateViews() {
return UI::EVENT_CONTINUE;
});
vtxCache->SetEnabledFunc([] {
- return !g_Config.bSoftwareRendering && g_Config.bHardwareTransform;
+ return !g_Config.bSoftwareRendering && g_Config.bHardwareTransform && g_Config.iGPUBackend != (int)GPUBackend::OPENGL;
});
CheckBox *texBackoff = graphicsSettings->Add(new CheckBox(&g_Config.bTextureBackoffCache, gr->T("Lazy texture caching", "Lazy texture caching (speedup)")));
texBackoff->SetDisabledPtr(&g_Config.bSoftwareRendering);
+ texBackoff->OnClick.Add([=](EventParams& e) {
+ settingInfo_->Show(gr->T("Lazy texture caching Tip", "Faster, but can cause text problems in a few games"), e.v);
+ return UI::EVENT_CONTINUE;
+ });
CheckBox *texSecondary_ = graphicsSettings->Add(new CheckBox(&g_Config.bTextureSecondaryCache, gr->T("Retain changed textures", "Retain changed textures (speedup, mem hog)")));
texSecondary_->OnClick.Add([=](EventParams &e) {
@@ -517,12 +544,18 @@ void GameSettingsScreen::CreateViews() {
// graphicsSettings->Add(new CheckBox(&g_Config.bFXAA, gr->T("FXAA")));
graphicsSettings->Add(new ItemHeader(gr->T("Texture Scaling")));
#ifndef MOBILE_DEVICE
- static const char *texScaleLevels[] = {"Auto", "Off", "2x", "3x", "4x", "5x", "6x"};
+ static const char *texScaleLevels[] = {"Off", "2x", "3x", "4x", "5x", "6x"};
#else
- static const char *texScaleLevels[] = {"Auto", "Off", "2x", "3x"};
+ static const char *texScaleLevels[] = {"Off", "2x", "3x"};
#endif
- PopupMultiChoice *texScalingChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexScalingLevel, gr->T("Upscale Level"), texScaleLevels, 0, ARRAY_SIZE(texScaleLevels), gr->GetName(), screenManager()));
+ if (!g_Config.bSimpleUI) {
+ static const char *texScaleAlgos[] = { "xBRZ", "Hybrid", "Bicubic", "Hybrid + Bicubic", };
+ PopupMultiChoice *texScalingType = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexScalingType, gr->T("Upscale Type"), texScaleAlgos, 0, ARRAY_SIZE(texScaleAlgos), gr->GetName(), screenManager()));
+ texScalingType->SetEnabledFunc([]() {
+ return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling();
+ });
+ PopupMultiChoice *texScalingChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexScalingLevel, gr->T("Upscale Level"), texScaleLevels, 1, ARRAY_SIZE(texScaleLevels), gr->GetName(), screenManager()));
// TODO: Better check? When it won't work, it scales down anyway.
if (!gl_extensions.OES_texture_npot && GetGPUBackend() == GPUBackend::OPENGL) {
texScalingChoice->HideChoice(3); // 3x
@@ -534,11 +567,7 @@ void GameSettingsScreen::CreateViews() {
}
return UI::EVENT_CONTINUE;
});
-
- if (!g_Config.bSimpleUI) {
- static const char *texScaleAlgos[] = { "xBRZ", "Hybrid", "Bicubic", "Hybrid + Bicubic" };
- PopupMultiChoice *texScalingType = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexScalingType, gr->T("Upscale Type"), texScaleAlgos, 0, ARRAY_SIZE(texScaleAlgos), gr->GetName(), screenManager()));
- texScalingType->SetEnabledFunc([]() {
+ texScalingChoice->SetEnabledFunc([]() {
return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling();
});
@@ -616,15 +645,9 @@ void GameSettingsScreen::CreateViews() {
dump->SetEnabled(false);
}
// Audio
- ViewGroup *audioSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
- audioSettingsScroll->SetTag("GameSettingsAudio");
- LinearLayout *audioSettings = new LinearLayoutList(ORIENT_VERTICAL);
- audioSettings->SetSpacing(0);
- audioSettingsScroll->Add(audioSettings);
- tabHolder->AddTab(ms->T("Audio"), audioSettingsScroll);
+ LinearLayout *audioSettings = AddTab("GameSettingsAudio", ms->T("Audio"));
audioSettings->Add(new ItemHeader(ms->T("Audio")));
-
audioSettings->Add(new CheckBox(&g_Config.bEnableSound, a->T("Enable Sound")));
PopupSliderChoice *volume = audioSettings->Add(new PopupSliderChoice(&g_Config.iGlobalVolume, VOLUME_OFF, VOLUME_FULL, a->T("Global volume"), screenManager()));
@@ -689,12 +712,8 @@ void GameSettingsScreen::CreateViews() {
atracmp3Vol->SetEnabledPtr(&g_Config.bEnableSound);
}
// Control
- ViewGroup *controlsSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
- controlsSettingsScroll->SetTag("GameSettingsControls");
- LinearLayout *controlsSettings = new LinearLayoutList(ORIENT_VERTICAL);
- controlsSettings->SetSpacing(0);
- controlsSettingsScroll->Add(controlsSettings);
- tabHolder->AddTab(ms->T("Controls"), controlsSettingsScroll);
+ LinearLayout *controlsSettings = AddTab("GameSettingsControls", ms->T("Controls"));
+
controlsSettings->Add(new ItemHeader(ms->T("Controls")));
controlsSettings->Add(new Choice(co->T("Control Mapping")))->OnClick.Handle(this, &GameSettingsScreen::OnControlMapping);
controlsSettings->Add(new Choice(co->T("Calibrate Analog Stick")))->OnClick.Handle(this, &GameSettingsScreen::OnCalibrateAnalogs);
@@ -799,12 +818,7 @@ void GameSettingsScreen::CreateViews() {
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSmoothing, 0.0f, 0.95f, co->T("Mouse smoothing"), 0.05f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bMouseControl);
#endif
- ViewGroup *networkingSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
- networkingSettingsScroll->SetTag("GameSettingsNetworking");
- LinearLayout *networkingSettings = new LinearLayoutList(ORIENT_VERTICAL);
- networkingSettings->SetSpacing(0);
- networkingSettingsScroll->Add(networkingSettings);
- tabHolder->AddTab(ms->T("Networking"), networkingSettingsScroll);
+ LinearLayout *networkingSettings = AddTab("GameSettingsNetworking", ms->T("Networking"));
networkingSettings->Add(new ItemHeader(ms->T("Networking")));
@@ -869,11 +883,13 @@ void GameSettingsScreen::CreateViews() {
#endif
#if defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
- qc1->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat0);
- qc2->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat1);
- qc3->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat2);
- qc4->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat3);
- qc5->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat4);
+ if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD)) {
+ qc1->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat0);
+ qc2->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat1);
+ qc3->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat2);
+ qc4->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat3);
+ qc5->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat4);
+ }
#endif
networkingSettings->Add(new ItemHeader(n->T("Misc", "Misc (default = compatibility)")));
@@ -881,12 +897,7 @@ void GameSettingsScreen::CreateViews() {
networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 0, 15000, n->T("Minimum Timeout", "Minimum Timeout (override in ms, 0 = default)"), 50, screenManager()));
networkingSettings->Add(new CheckBox(&g_Config.bForcedFirstConnect, n->T("Forced First Connect", "Forced First Connect (faster Connect)")));
- ViewGroup *toolsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
- toolsScroll->SetTag("GameSettingsTools");
- LinearLayout *tools = new LinearLayoutList(ORIENT_VERTICAL);
- tools->SetSpacing(0);
- toolsScroll->Add(tools);
- tabHolder->AddTab(ms->T("Tools"), toolsScroll);
+ LinearLayout *tools = AddTab("GameSettingsTools", ms->T("Tools"));
tools->Add(new ItemHeader(ms->T("Tools")));
// These were moved here so use the wrong translation objects, to avoid having to change all inis... This isn't a sustainable situation :P
@@ -899,12 +910,7 @@ void GameSettingsScreen::CreateViews() {
tools->Add(new Choice(ri->T("Remote disc streaming")))->OnClick.Handle(this, &GameSettingsScreen::OnRemoteISO);
// System
- ViewGroup *systemSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
- systemSettingsScroll->SetTag("GameSettingsSystem");
- LinearLayout *systemSettings = new LinearLayoutList(ORIENT_VERTICAL);
- systemSettings->SetSpacing(0);
- systemSettingsScroll->Add(systemSettings);
- tabHolder->AddTab(ms->T("System"), systemSettingsScroll);
+ LinearLayout *systemSettings = AddTab("GameSettingsSystem", ms->T("System"));
if (!g_Config.bSimpleUI) {
systemSettings->Add(new ItemHeader(sy->T("UI")));
@@ -925,41 +931,28 @@ void GameSettingsScreen::CreateViews() {
static const char *backgroundAnimations[] = { "No animation", "Floating symbols", "Recent games", "Waves", "Moving background" };
systemSettings->Add(new PopupMultiChoice(&g_Config.iBackgroundAnimation, sy->T("UI background animation"), backgroundAnimations, 0, ARRAY_SIZE(backgroundAnimations), sy->GetName(), screenManager()));
- systemSettings->Add(new ItemHeader(sy->T("Help the PPSSPP team")));
- if (!enableReportsSet_)
- enableReports_ = Reporting::IsEnabled();
- enableReportsSet_ = true;
- enableReportsCheckbox_ = new CheckBox(&enableReports_, sy->T("Enable Compatibility Server Reports"));
- enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
- systemSettings->Add(enableReportsCheckbox_);
-
- systemSettings->Add(new ItemHeader(sy->T("Emulation")));
-
- systemSettings->Add(new CheckBox(&g_Config.bFastMemory, sy->T("Fast Memory", "Fast Memory")))->OnClick.Handle(this, &GameSettingsScreen::OnJitAffectingSetting);
- systemSettings->Add(new CheckBox(&g_Config.bIgnoreBadMemAccess, sy->T("Ignore bad memory accesses")));
-
- systemSettings->Add(new CheckBox(&g_Config.bSeparateIOThread, sy->T("I/O on thread (experimental)")))->SetEnabled(!PSP_IsInited());
- static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)", "Simulate UMD delays" };
- View *ioTimingMethod = systemSettings->Add(new PopupMultiChoice(&g_Config.iIOTimingMethod, sy->T("IO timing method"), ioTimingMethods, 0, ARRAY_SIZE(ioTimingMethods), sy->GetName(), screenManager()));
- ioTimingMethod->SetEnabledPtr(&g_Config.bSeparateIOThread);
- systemSettings->Add(new CheckBox(&g_Config.bForceLagSync, sy->T("Force real clock sync (slower, less lag)")));
- PopupSliderChoice *lockedMhz = systemSettings->Add(new PopupSliderChoice(&g_Config.iLockedCPUSpeed, 0, 1000, sy->T("Change CPU Clock", "Change CPU Clock (unstable)"), screenManager(), sy->T("MHz, 0:default")));
- lockedMhz->OnChange.Add([&](UI::EventParams &) {
- enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
- return UI::EVENT_CONTINUE;
- });
- lockedMhz->SetZeroLabel(sy->T("Auto"));
- PopupSliderChoice *rewindFreq = systemSettings->Add(new PopupSliderChoice(&g_Config.iRewindFlipFrequency, 0, 1800, sy->T("Rewind Snapshot Frequency", "Rewind Snapshot Frequency (mem hog)"), screenManager(), sy->T("frames, 0:off")));
- rewindFreq->SetZeroLabel(sy->T("Off"));
-
systemSettings->Add(new ItemHeader(sy->T("PSP Memory Stick")));
+#if (defined(USING_QT_UI) || PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)) && !PPSSPP_PLATFORM(UWP)
+ systemSettings->Add(new Choice(sy->T("Show Memory Stick folder")))->OnClick.Handle(this, &GameSettingsScreen::OnOpenMemStick);
+#endif
+
}
#if PPSSPP_PLATFORM(ANDROID)
memstickDisplay_ = g_Config.memStickDirectory.ToVisualString();
- auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Change Memory Stick folder", "Memory Stick folder"), (const char *)nullptr));
+ auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Memory Stick folder", "Memory Stick folder"), (const char *)nullptr));
memstickPath->SetEnabled(!PSP_IsInited());
memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnChangeMemStickDir);
+
+ // Display USB path for convenience.
+ std::string usbPath;
+ if (PathToVisualUsbPath(g_Config.memStickDirectory, usbPath)) {
+ if (usbPath.empty()) {
+ // Probably it's just the root. So let's add PSP to make it clear.
+ usbPath = "/PSP";
+ }
+ systemSettings->Add(new InfoItem(sy->T("USB"), usbPath))->SetChoiceStyle(true);
+ }
#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
SavePathInMyDocumentChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Save path in My Documents", "Save path in My Documents")));
SavePathInMyDocumentChoice->SetEnabled(!PSP_IsInited());
@@ -1005,7 +998,33 @@ void GameSettingsScreen::CreateViews() {
}
#endif
systemSettings->Add(new CheckBox(&g_Config.bMemStickInserted, sy->T("Memory Stick inserted")));
- systemSettings->Add(new PopupSliderChoice(&g_Config.iMemStickSizeGB, 1, 32, sy->T("Change Memory Stick Size", "Memory Stick Size (GB)"), screenManager(), "GB"));
+ UI::PopupSliderChoice *sizeChoice = systemSettings->Add(new PopupSliderChoice(&g_Config.iMemStickSizeGB, 1, 32, sy->T("Memory Stick size", "Memory Stick size"), screenManager(), "GB"));
+ sizeChoice->SetFormat("%d GB");
+
+ systemSettings->Add(new ItemHeader(sy->T("Help the PPSSPP team")));
+ if (!enableReportsSet_)
+ enableReports_ = Reporting::IsEnabled();
+ enableReportsSet_ = true;
+ enableReportsCheckbox_ = new CheckBox(&enableReports_, sy->T("Enable Compatibility Server Reports"));
+ enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
+ systemSettings->Add(enableReportsCheckbox_);
+
+ systemSettings->Add(new ItemHeader(sy->T("Emulation")));
+
+ systemSettings->Add(new CheckBox(&g_Config.bFastMemory, sy->T("Fast Memory", "Fast Memory")))->OnClick.Handle(this, &GameSettingsScreen::OnJitAffectingSetting);
+ systemSettings->Add(new CheckBox(&g_Config.bIgnoreBadMemAccess, sy->T("Ignore bad memory accesses")));
+
+ static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)", "Simulate UMD delays" };
+ View *ioTimingMethod = systemSettings->Add(new PopupMultiChoice(&g_Config.iIOTimingMethod, sy->T("IO timing method"), ioTimingMethods, 0, ARRAY_SIZE(ioTimingMethods), sy->GetName(), screenManager()));
+ systemSettings->Add(new CheckBox(&g_Config.bForceLagSync, sy->T("Force real clock sync (slower, less lag)")));
+ PopupSliderChoice *lockedMhz = systemSettings->Add(new PopupSliderChoice(&g_Config.iLockedCPUSpeed, 0, 1000, sy->T("Change CPU Clock", "Change CPU Clock (unstable)"), screenManager(), sy->T("MHz, 0:default")));
+ lockedMhz->OnChange.Add([&](UI::EventParams &) {
+ enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
+ return UI::EVENT_CONTINUE;
+ });
+ lockedMhz->SetZeroLabel(sy->T("Auto"));
+ PopupSliderChoice *rewindFreq = systemSettings->Add(new PopupSliderChoice(&g_Config.iRewindFlipFrequency, 0, 1800, sy->T("Rewind Snapshot Frequency", "Rewind Snapshot Frequency (mem hog)"), screenManager(), sy->T("frames, 0:off")));
+ rewindFreq->SetZeroLabel(sy->T("Off"));
systemSettings->Add(new ItemHeader(sy->T("General")));
@@ -1027,9 +1046,8 @@ void GameSettingsScreen::CreateViews() {
systemSettings->Add(new CheckBox(&g_Config.bEnableStateUndo, sy->T("Savestate slot backups")));
static const char *autoLoadSaveStateChoices[] = { "Off", "Oldest Save", "Newest Save", "Slot 1", "Slot 2", "Slot 3", "Slot 4", "Slot 5" };
systemSettings->Add(new PopupMultiChoice(&g_Config.iAutoLoadSaveState, sy->T("Auto Load Savestate"), autoLoadSaveStateChoices, 0, ARRAY_SIZE(autoLoadSaveStateChoices), sy->GetName(), screenManager()));
-#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
- systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Use system native keyboard")));
-#endif
+ if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD))
+ systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Use system native keyboard")));
#if PPSSPP_ARCH(AMD64)
systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM")))->SetEnabled(!PSP_IsInited());
@@ -1053,7 +1071,10 @@ void GameSettingsScreen::CreateViews() {
#if !defined(MOBILE_DEVICE) && !defined(USING_QT_UI) // TODO: Add all platforms where KEY_CHAR support is added
systemSettings->Add(new PopupTextInputChoice(&g_Config.sNickName, sy->T("Change Nickname"), "", 32, screenManager()));
#elif PPSSPP_PLATFORM(ANDROID)
- systemSettings->Add(new ChoiceWithValueDisplay(&g_Config.sNickName, sy->T("Change Nickname"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeNickname);
+ if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD))
+ systemSettings->Add(new ChoiceWithValueDisplay(&g_Config.sNickName, sy->T("Change Nickname"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeNickname);
+ else
+ systemSettings->Add(new PopupTextInputChoice(&g_Config.sNickName, sy->T("Change Nickname"), "", 32, screenManager()));
#endif
if (!g_Config.bSimpleUI) {
systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG")));
@@ -1068,16 +1089,52 @@ void GameSettingsScreen::CreateViews() {
}
systemSettings->Add(new CheckBox(&g_Config.bSaveLoadResetsAVdumping, sy->T("Reset Recording on Save/Load State")));
#endif
- if (!g_Config.bSimpleUI) {
+if (!g_Config.bSimpleUI) {
systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Day Light Saving")));
- static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY" };
+ static const char* dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY" };
systemSettings->Add(new PopupMultiChoice(&g_Config.iDateFormat, sy->T("Date Format"), dateFormat, 0, 3, sy->GetName(), screenManager()));
- static const char *timeFormat[] = { "24HR", "12HR" };
+ static const char* timeFormat[] = { "24HR", "12HR" };
systemSettings->Add(new PopupMultiChoice(&g_Config.iTimeFormat, sy->T("Time Format"), timeFormat, 0, 2, sy->GetName(), screenManager()));
- static const char *buttonPref[] = { "Use O to confirm", "Use X to confirm" };
+ static const char* buttonPref[] = { "Use O to confirm", "Use X to confirm" };
systemSettings->Add(new PopupMultiChoice(&g_Config.iButtonPreference, sy->T("Confirmation Button"), buttonPref, 0, 2, sy->GetName(), screenManager()));
+
+ if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD)) {
+ // Search
+ LinearLayout* searchSettings = AddTab("GameSettingsSearch", ms->T("Search"), true);
+
+ searchSettings->Add(new ItemHeader(se->T("Find settings")));
+ searchSettings->Add(new ChoiceWithValueDisplay(&searchFilter_, se->T("Filter"), (const char*)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeSearchFilter);
+ clearSearchChoice_ = searchSettings->Add(new Choice(se->T("Clear filter")));
+ clearSearchChoice_->OnClick.Handle(this, &GameSettingsScreen::OnClearSearchFilter);
+ noSearchResults_ = searchSettings->Add(new TextView(se->T("No settings matched '%1'"), new LinearLayoutParams(Margins(20, 5))));
+
+ ApplySearchFilter();
}
}
+}
+
+UI::LinearLayout *GameSettingsScreen::AddTab(const char *tag, const std::string &title, bool isSearch) {
+ auto se = GetI18NCategory("Search");
+
+ using namespace UI;
+ ViewGroup *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
+ scroll->SetTag(tag);
+
+ LinearLayout *contents = new LinearLayoutList(ORIENT_VERTICAL);
+ contents->SetSpacing(0);
+ scroll->Add(contents);
+ tabHolder_->AddTab(title, scroll);
+
+ if (!isSearch) {
+ settingTabContents_.push_back(contents);
+
+ auto notice = contents->Add(new TextView(se->T("Filtering settings by '%1'"), new LinearLayoutParams(Margins(20, 5))));
+ notice->SetVisibility(V_GONE);
+ settingTabFilterNotices_.push_back(notice);
+ }
+
+ return contents;
+}
UI::EventReturn GameSettingsScreen::OnAutoFrameskip(UI::EventParams &e) {
if (g_Config.bAutoFrameSkip && g_Config.iFrameSkip == 0) {
@@ -1162,6 +1219,11 @@ UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) {
return UI::EVENT_DONE;
}
+UI::EventReturn GameSettingsScreen::OnOpenMemStick(UI::EventParams &e) {
+ OpenDirectory(File::ResolvePath(g_Config.memStickDirectory.ToString().c_str()).c_str());
+ return UI::EVENT_DONE;
+}
+
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) {
@@ -1316,6 +1378,50 @@ void GameSettingsScreen::sendMessage(const char *message, const char *value) {
g_Config.bShaderChainRequires60FPS = PostShaderChainRequires60FPS(GetFullPostShadersChain(g_Config.vPostShaderNames));
RecreateViews();
}
+ if (!strcmp(message, "gameSettings_search")) {
+ std::string filter = value ? value : "";
+ searchFilter_.resize(filter.size());
+ std::transform(filter.begin(), filter.end(), searchFilter_.begin(), tolower);
+
+ ApplySearchFilter();
+ }
+}
+
+void GameSettingsScreen::ApplySearchFilter() {
+ auto se = GetI18NCategory("Search");
+
+ bool matches = searchFilter_.empty();
+ for (int t = 0; t < (int)settingTabContents_.size(); ++t) {
+ auto tabContents = settingTabContents_[t];
+ bool tabMatches = searchFilter_.empty();
+
+ // Show an indicator that a filter is applied.
+ settingTabFilterNotices_[t]->SetVisibility(tabMatches ? UI::V_GONE : UI::V_VISIBLE);
+ settingTabFilterNotices_[t]->SetText(ReplaceAll(se->T("Filtering settings by '%1'"), "%1", searchFilter_));
+
+ UI::View *lastHeading = nullptr;
+ for (int i = 1; i < tabContents->GetNumSubviews(); ++i) {
+ UI::View *v = tabContents->GetViewByIndex(i);
+ if (!v->CanBeFocused()) {
+ lastHeading = v;
+ }
+
+ std::string label = v->DescribeText();
+ std::transform(label.begin(), label.end(), label.begin(), tolower);
+ bool match = v->CanBeFocused() && label.find(searchFilter_) != label.npos;
+ tabMatches = tabMatches || match;
+
+ if (match && lastHeading)
+ lastHeading->SetVisibility(UI::V_VISIBLE);
+ v->SetVisibility(searchFilter_.empty() || match ? UI::V_VISIBLE : UI::V_GONE);
+ }
+ tabHolder_->EnableTab(t, tabMatches);
+ matches = matches || tabMatches;
+ }
+
+ noSearchResults_->SetText(ReplaceAll(se->T("No settings matched '%1'"), "%1", searchFilter_));
+ noSearchResults_->SetVisibility(matches ? UI::V_GONE : UI::V_VISIBLE);
+ clearSearchChoice_->SetVisibility(searchFilter_.empty() ? UI::V_GONE : UI::V_VISIBLE);
}
void GameSettingsScreen::dialogFinished(const Screen *dialog, DialogResult result) {
@@ -1642,6 +1748,23 @@ UI::EventReturn GameSettingsScreen::OnSysInfo(UI::EventParams &e) {
return UI::EVENT_DONE;
}
+UI::EventReturn GameSettingsScreen::OnChangeSearchFilter(UI::EventParams &e) {
+#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
+ auto se = GetI18NCategory("Search");
+ System_InputBoxGetString(se->T("Search term"), searchFilter_, [this](bool result, const std::string &value) {
+ if (result) {
+ NativeMessageReceived("gameSettings_search", StripSpaces(value).c_str());
+ }
+ });
+#endif
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn GameSettingsScreen::OnClearSearchFilter(UI::EventParams &e) {
+ NativeMessageReceived("gameSettings_search", "");
+ return UI::EVENT_DONE;
+}
+
void DeveloperToolsScreen::CreateViews() {
using namespace UI;
root_ = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT));
@@ -1944,7 +2067,8 @@ void HostnameSelectScreen::CreatePopupContents(UI::ViewGroup *parent) {
buttonsRow2->Add(new Spacer(new LinearLayoutParams(1.0, G_LEFT)));
#if PPSSPP_PLATFORM(ANDROID)
- buttonsRow2->Add(new Button(di->T("Edit")))->OnClick.Handle(this, &HostnameSelectScreen::OnEditClick); // Since we don't have OnClick Event triggered from TextEdit's Touch().. here we go!
+ if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD))
+ buttonsRow2->Add(new Button(di->T("Edit")))->OnClick.Handle(this, &HostnameSelectScreen::OnEditClick);
#endif
buttonsRow2->Add(new Button(di->T("Delete")))->OnClick.Handle(this, &HostnameSelectScreen::OnDeleteClick);
buttonsRow2->Add(new Button(di->T("Delete all")))->OnClick.Handle(this, &HostnameSelectScreen::OnDeleteAllClick);
@@ -2130,9 +2254,8 @@ void GestureMappingScreen::CreateViews() {
auto mc = GetI18NCategory("MappableControls");
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
- Choice *back = new Choice(di->T("Back"), "", false, new AnchorLayoutParams(130, WRAP_CONTENT, 10, NONE, NONE, 10));
- root_->Add(back)->OnClick.Handle(this, &UIScreen::OnBack);
- TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 140, new AnchorLayoutParams(10, 0, 10, 0, false));
+ AddStandardBack(root_);
+ TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
tabHolder->AddTab(co->T("Gesture"), rightPanel);
diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h
index 743c8e562c..0cddc7f11b 100644
--- a/UI/GameSettingsScreen.h
+++ b/UI/GameSettingsScreen.h
@@ -47,6 +47,8 @@ protected:
void RecreateViews() override;
private:
+ UI::LinearLayout *AddTab(const char *tag, const std::string &title, bool isSearch = false);
+ void ApplySearchFilter();
void TriggerRestart(const char *why);
std::string gameID_;
@@ -59,6 +61,8 @@ private:
UI::PopupMultiChoice *resolutionChoice_;
UI::CheckBox *frameSkipAuto_;
SettingInfoMessage *settingInfo_;
+ UI::Choice *clearSearchChoice_;
+ UI::TextView *noSearchResults_;
#ifdef _WIN32
UI::CheckBox *SavePathInMyDocumentChoice;
UI::CheckBox *SavePathInOtherChoice;
@@ -69,6 +73,10 @@ private:
std::string memstickDisplay_;
+ UI::TabHolder *tabHolder_;
+ std::vector settingTabContents_;
+ std::vector settingTabFilterNotices_;
+
// Event handlers
UI::EventReturn OnControlMapping(UI::EventParams &e);
UI::EventReturn OnCalibrateAnalogs(UI::EventParams &e);
@@ -110,6 +118,7 @@ private:
UI::EventReturn OnAudioDevice(UI::EventParams &e);
UI::EventReturn OnJitAffectingSetting(UI::EventParams &e);
UI::EventReturn OnChangeMemStickDir(UI::EventParams &e);
+ UI::EventReturn OnOpenMemStick(UI::EventParams &e);
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
UI::EventReturn OnSavePathMydoc(UI::EventParams &e);
UI::EventReturn OnSavePathOther(UI::EventParams &e);
@@ -125,6 +134,8 @@ private:
UI::EventReturn OnSavedataManager(UI::EventParams &e);
UI::EventReturn OnSysInfo(UI::EventParams &e);
+ UI::EventReturn OnChangeSearchFilter(UI::EventParams &e);
+ UI::EventReturn OnClearSearchFilter(UI::EventParams &e);
// Temporaries to convert setting types, cache enabled, etc.
int iAlternateSpeedPercent1_;
@@ -134,6 +145,7 @@ private:
bool enableReportsSet_ = false;
bool tessHWEnable_;
std::string shaderNames_[256];
+ std::string searchFilter_;
//edit the game-specific settings and restore the global settings after exiting
bool editThenRestore_;
diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp
index 63f36632e3..4089dc9390 100644
--- a/UI/GamepadEmu.cpp
+++ b/UI/GamepadEmu.cpp
@@ -97,16 +97,13 @@ void MultiTouchButton::GetContentDimensions(const UIContext &dc, float &w, float
}
void MultiTouchButton::Touch(const TouchInput &input) {
- if (analogPointerMask & (1 << input.id))
- return;
-
GamepadView::Touch(input);
if ((input.flags & TOUCH_DOWN) && bounds_.Contains(input.x, input.y)) {
pointerDownMask_ |= 1 << input.id;
usedPointerMask |= 1 << input.id;
}
if (input.flags & TOUCH_MOVE) {
- if (bounds_.Contains(input.x, input.y))
+ if (bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id)))
pointerDownMask_ |= 1 << input.id;
else
pointerDownMask_ &= ~(1 << input.id);
@@ -156,9 +153,6 @@ void MultiTouchButton::Draw(UIContext &dc) {
}
void BoolButton::Touch(const TouchInput &input) {
- if (analogPointerMask & (1 << input.id))
- return;
-
bool lastDown = pointerDownMask_ != 0;
MultiTouchButton::Touch(input);
bool down = pointerDownMask_ != 0;
@@ -172,9 +166,6 @@ void BoolButton::Touch(const TouchInput &input) {
}
void PSPButton::Touch(const TouchInput &input) {
- if (analogPointerMask & (1 << input.id))
- return;
-
bool lastDown = pointerDownMask_ != 0;
MultiTouchButton::Touch(input);
bool down = pointerDownMask_ != 0;
@@ -202,9 +193,6 @@ void ComboKey::GetContentDimensions(const UIContext &dc, float &w, float &h) con
}
void ComboKey::Touch(const TouchInput &input) {
- if (analogPointerMask & (1 << input.id))
- return;
-
using namespace CustomKey;
bool lastDown = pointerDownMask_ != 0;
MultiTouchButton::Touch(input);
@@ -244,9 +232,6 @@ void PSPDpad::GetContentDimensions(const UIContext &dc, float &w, float &h) cons
}
void PSPDpad::Touch(const TouchInput &input) {
- if (analogPointerMask & (1 << input.id))
- return;
-
GamepadView::Touch(input);
if (input.flags & TOUCH_DOWN) {
@@ -257,7 +242,7 @@ void PSPDpad::Touch(const TouchInput &input) {
}
}
if (input.flags & TOUCH_MOVE) {
- if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y)) {
+ if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id))) {
dragPointerId_ = input.id;
}
if (input.id == dragPointerId_) {
diff --git a/UI/GamepadEmu.h b/UI/GamepadEmu.h
index 655fbaf7c3..3b320b3ce8 100644
--- a/UI/GamepadEmu.h
+++ b/UI/GamepadEmu.h
@@ -212,44 +212,42 @@ protected:
namespace CustomKey {
// Image list
struct keyImage {
- const char* n; // UI name
ImageID i; // ImageID
float r; // Rotation angle in degree
};
static const keyImage comboKeyImages[] = {
- { "1", ImageID("I_1"), 0.0f },
- { "2", ImageID("I_2"), 0.0f },
- { "3", ImageID("I_3"), 0.0f },
- { "4", ImageID("I_4"), 0.0f },
- { "5", ImageID("I_5"), 0.0f },
- { "6", ImageID("I_6"), 0.0f },
- { "A", ImageID("I_A"), 0.0f },
- { "B", ImageID("I_B"), 0.0f },
- { "C", ImageID("I_C"), 0.0f },
- { "D", ImageID("I_D"), 0.0f },
- { "E", ImageID("I_E"), 0.0f },
- { "F", ImageID("I_F"), 0.0f },
- { "Circle", ImageID("I_CIRCLE"), 0.0f },
- { "Cross", ImageID("I_CROSS"), 0.0f },
- { "Square", ImageID("I_SQUARE"), 0.0f },
- { "Triangle", ImageID("I_TRIANGLE"), 0.0f },
- { "L", ImageID("I_L"), 0.0f },
- { "R", ImageID("I_R"), 0.0f },
- { "Start", ImageID("I_START"), 0.0f },
- { "Select", ImageID("I_SELECT"), 0.0f },
- { "Plus", ImageID("I_CROSS"), 45.0f },
- { "Rhombus", ImageID("I_SQUARE"), 45.0f },
- { "Down Triangle", ImageID("I_TRIANGLE"), 180.0f },
- { "Arrow up", ImageID("I_ARROW"), 90.0f},
- { "Arrow down", ImageID("I_ARROW"), 270.0f},
- { "Arrow left", ImageID("I_ARROW"), 0.0f},
- { "Arrow right", ImageID("I_ARROW"), 180.0f},
- { "Gear", ImageID("I_GEAR"), 0.0f},
+ { ImageID("I_1"), 0.0f },
+ { ImageID("I_2"), 0.0f },
+ { ImageID("I_3"), 0.0f },
+ { ImageID("I_4"), 0.0f },
+ { ImageID("I_5"), 0.0f },
+ { ImageID("I_6"), 0.0f },
+ { ImageID("I_A"), 0.0f },
+ { ImageID("I_B"), 0.0f },
+ { ImageID("I_C"), 0.0f },
+ { ImageID("I_D"), 0.0f },
+ { ImageID("I_E"), 0.0f },
+ { ImageID("I_F"), 0.0f },
+ { ImageID("I_CIRCLE"), 0.0f },
+ { ImageID("I_CROSS"), 0.0f },
+ { ImageID("I_SQUARE"), 0.0f },
+ { ImageID("I_TRIANGLE"), 0.0f },
+ { ImageID("I_L"), 0.0f },
+ { ImageID("I_R"), 0.0f },
+ { ImageID("I_START"), 0.0f },
+ { ImageID("I_SELECT"), 0.0f },
+ { ImageID("I_CROSS"), 45.0f },
+ { ImageID("I_SQUARE"), 45.0f },
+ { ImageID("I_TRIANGLE"), 180.0f },
+ { ImageID("I_ARROW"), 90.0f},
+ { ImageID("I_ARROW"), 270.0f},
+ { ImageID("I_ARROW"), 0.0f},
+ { ImageID("I_ARROW"), 180.0f},
+ { ImageID("I_GEAR"), 0.0f},
};
// Shape list
struct keyShape {
- const char* n; // UI name
ImageID i; // ImageID
ImageID l; // ImageID line version
float r; // Rotation angle in dregree
@@ -257,15 +255,16 @@ namespace CustomKey {
bool d; // Invert height and width for context dimension (for example for 90 degree rot)
};
static const keyShape comboKeyShapes[] = {
- { "Circle", ImageID("I_ROUND"), ImageID("I_ROUND_LINE"), 0.0f, false, false },
- { "Rectangle", ImageID("I_RECT"), ImageID("I_RECT_LINE"), 0.0f, false, false },
- { "Vertical Rectangle", ImageID("I_RECT"), ImageID("I_RECT_LINE"), 90.0f, false, true },
- { "L button", ImageID("I_SHOULDER"), ImageID("I_SHOULDER_LINE"), 0.0f, false, false },
- { "R button", ImageID("I_SHOULDER"), ImageID("I_SHOULDER_LINE"), 0.0f, true, false },
- { "Arrow up", ImageID("I_DIR"), ImageID("I_DIR_LINE"), 270.0f, false, true },
- { "Arrow down", ImageID("I_DIR"), ImageID("I_DIR_LINE"), 90.0f, false, true },
- { "Arrow left", ImageID("I_DIR"), ImageID("I_DIR_LINE"), 180.0f, false, false },
- { "Arrow right", ImageID("I_DIR"), ImageID("I_DIR_LINE"), 0.0f, false, false },
+ { ImageID("I_ROUND"), ImageID("I_ROUND_LINE"), 0.0f, false, false },
+ { ImageID("I_RECT"), ImageID("I_RECT_LINE"), 0.0f, false, false },
+ { ImageID("I_RECT"), ImageID("I_RECT_LINE"), 90.0f, false, true },
+ { ImageID("I_SHOULDER"), ImageID("I_SHOULDER_LINE"), 0.0f, false, false },
+ { ImageID("I_SHOULDER"), ImageID("I_SHOULDER_LINE"), 0.0f, true, false },
+ { ImageID("I_DIR"), ImageID("I_DIR_LINE"), 270.0f, false, true },
+ { ImageID("I_DIR"), ImageID("I_DIR_LINE"), 90.0f, false, true },
+ { ImageID("I_DIR"), ImageID("I_DIR_LINE"), 180.0f, false, false },
+ { ImageID("I_DIR"), ImageID("I_DIR_LINE"), 0.0f, false, false },
+ { ImageID("I_SQUARE_SHAPE"), ImageID("I_SQUARE_SHAPE_LINE"), 0.0f, false, false },
};
// Button list
diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp
index 93ff0816df..6188f3459d 100644
--- a/UI/MainScreen.cpp
+++ b/UI/MainScreen.cpp
@@ -341,7 +341,7 @@ void GameButton::Draw(UIContext &dc) {
dc.MeasureText(dc.GetFontStyle(), 0.7f, 0.7f, ginfo->id_version.c_str(), &vw, &vh, 0);
availableWidth -= vw + 20;
dc.SetFontScale(0.7f, 0.7f);
- dc.DrawText(ginfo->id_version.c_str(), availableWidth + 160, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
+ dc.DrawText(ginfo->id_version.c_str(), bounds_.x + availableWidth + 160, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
}
float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
@@ -680,7 +680,7 @@ void GameBrowser::Refresh() {
topBar->Add(new Choice(ImageID("I_SDCARD"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::StorageClick);
}
if (System_GetPropertyBool(SYSPROP_HAS_FOLDER_BROWSER)) {
- topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::BrowseClick);
+ topBar->Add(new Choice(mm->T("Browse"), ImageID("I_FOLDER_OPEN"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::BrowseClick);
}
} else {
topBar->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f)));
@@ -1007,7 +1007,7 @@ void MainScreen::CreateViews() {
Button *focusButton = nullptr;
if (hasStorageAccess) {
- scrollAllGames_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT), true);
+ scrollAllGames_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollAllGames_->SetTag("MainScreenAllGames");
ScrollView *scrollHomebrew = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollHomebrew->SetTag("MainScreenHomebrew");
@@ -1026,7 +1026,7 @@ void MainScreen::CreateViews() {
tabHolder_->AddTab(mm->T("Games"), scrollAllGames_);
tabHolder_->AddTab(mm->T("Homebrew & Demos"), scrollHomebrew);
- scrollAllGames_->ScrollTo(g_Config.fGameListScrollPosition);
+ scrollAllGames_->RememberPosition(&g_Config.fGameListScrollPosition);
tabAllGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
tabHomebrew->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
@@ -1245,9 +1245,6 @@ void MainScreen::update() {
RecreateViews();
lastVertical_ = vertical;
}
- if (scrollAllGames_) {
- g_Config.fGameListScrollPosition = scrollAllGames_->GetScrollPosition();
- }
}
bool MainScreen::UseVerticalLayout() const {
diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp
index 8f15edc91d..ef617c5b5c 100644
--- a/UI/MemStickScreen.cpp
+++ b/UI/MemStickScreen.cpp
@@ -38,16 +38,17 @@
#include "Common/Thread/ThreadManager.h"
-#include "Core/Util/GameManager.h"
-#include "Core/System.h"
#include "Core/Config.h"
+#include "Core/Reporting.h"
+#include "Core/System.h"
+#include "Core/Util/GameManager.h"
#include "UI/MemStickScreen.h"
#include "UI/MainScreen.h"
#include "UI/MiscScreens.h"
static bool FolderSeemsToBeUsed(Path newMemstickFolder) {
- // Inspect the potential new folder.
+ // Inspect the potential new folder, quickly.
if (File::Exists(newMemstickFolder / "PSP/SAVEDATA") || File::Exists(newMemstickFolder / "SAVEDATA")) {
// Does seem likely. We could add more criteria like checking for actual savegames or something.
return true;
@@ -79,7 +80,7 @@ static bool SwitchMemstickFolderTo(Path newMemstickFolder) {
std::string str = newMemstickFolder.ToString();
if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) {
ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", newMemstickFolder.c_str(), memStickDirFile.c_str());
- // Not sure what to do if this file.
+ // Not sure what to do if this file can't be written. Disk full?
}
// Save so the settings, at least, are transferred.
@@ -102,6 +103,86 @@ static std::string FormatSpaceString(int64_t space) {
MemStickScreen::MemStickScreen(bool initialSetup)
: initialSetup_(initialSetup) {
+#if PPSSPP_PLATFORM(ANDROID)
+ // Let's only offer the browse-for-folder choice on Android 10 or later.
+ // Earlier versions often don't really have working folder browsers.
+ storageBrowserWorking_ = System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 29;
+#else
+ // For testing UI only
+ storageBrowserWorking_ = true;
+#endif
+
+ if (initialSetup_) {
+ // Preselect current choice.
+ if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
+ choice_ = CHOICE_BROWSE_FOLDER;
+ } else {
+ WARN_LOG_REPORT(SYSTEM, "Scoped storage not enabled - shouldn't be in MemStickScreen at initial setup");
+ choice_ = CHOICE_STORAGE_ROOT;
+ // Shouldn't really be here in initial setup.
+ }
+ } else {
+ // Detect the current choice, so it's preselected in the UI.
+ if (g_Config.memStickDirectory == Path(g_extFilesDir)) {
+ choice_ = CHOICE_PRIVATE_DIRECTORY;
+ } else if (g_Config.memStickDirectory == Path(g_externalDir)) {
+ choice_ = CHOICE_STORAGE_ROOT;
+ } else if (storageBrowserWorking_) {
+ choice_ = CHOICE_BROWSE_FOLDER;
+ } else {
+ choice_ = CHOICE_SET_MANUAL;
+ }
+ }
+}
+
+static void AddExplanation(UI::ViewGroup *viewGroup, MemStickScreen::Choice choice, UI::View *extraView = nullptr) {
+ auto iz = GetI18NCategory("MemStick");
+ using namespace UI;
+
+ int flags = FLAG_WRAP_TEXT;
+
+ UI::ViewGroup *holder = new UI::LinearLayout(ORIENT_VERTICAL);
+
+ UI::ViewGroup *indentHolder = new UI::LinearLayout(ORIENT_HORIZONTAL);
+ indentHolder->Add(new Spacer(20.0));
+ indentHolder->Add(holder);
+
+ viewGroup->Add(indentHolder);
+
+ if (extraView) {
+ holder->Add(extraView);
+ }
+
+ switch (choice) {
+ case MemStickScreen::CHOICE_STORAGE_ROOT:
+ // Old school choice
+ holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true);
+ holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true);
+ holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
+ break;
+ case MemStickScreen::CHOICE_BROWSE_FOLDER:
+ holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true);
+ holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true);
+ holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
+ break;
+ case MemStickScreen::CHOICE_PRIVATE_DIRECTORY:
+ // Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic?
+ holder->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!"), flags, false))->SetBullet(true);
+ holder->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!"), flags, false))->SetBullet(true);
+#if GOLD
+ holder->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files"), flags, false))->SetBullet(true);
+#else
+ holder->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files"), flags, false))->SetBullet(true);
+#endif
+ break;
+ case MemStickScreen::CHOICE_SET_MANUAL:
+ default:
+ holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
+ // What more?
+
+ // Should we have a special text here? It'll popup a text window for editing.
+ break;
+ }
}
void MemStickScreen::CreateViews() {
@@ -110,78 +191,146 @@ void MemStickScreen::CreateViews() {
auto di = GetI18NCategory("Dialog");
auto iz = GetI18NCategory("MemStick");
- Margins actionMenuMargins(15, 15, 15, 0);
+ Margins actionMenuMargins(15, 0, 15, 0);
root_ = new LinearLayout(ORIENT_HORIZONTAL);
Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f));
- ScrollView *leftColumnScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
+ ScrollView *mainColumnScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
+
+ ViewGroup *mainColumn = new LinearLayoutList(ORIENT_VERTICAL);
+ mainColumnScroll->Add(mainColumn);
- ViewGroup *leftColumn = new LinearLayoutList(ORIENT_VERTICAL);
- ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
root_->Add(spacerColumn);
- root_->Add(leftColumnScroll);
- root_->Add(rightColumnItems);
-
- leftColumnScroll->Add(leftColumn);
+ root_->Add(mainColumnScroll);
if (initialSetup_) {
- leftColumn->Add(new TextView(iz->T("Welcome to PPSSPP!"), ALIGN_LEFT, false));
- leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
+ mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
+ mainColumn->Add(new TextView(iz->T("Welcome to PPSSPP!"), ALIGN_LEFT, false));
}
- if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
- leftColumn->Add(new TextView(iz->T("ScopedStorageWarning", "WARNING: BETA ANDROID SCOPED STORAGE SUPPORT\nMAY EAT YOUR DATA"), ALIGN_LEFT, false));
- }
+ mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 18.0f, 0.0f)));
- leftColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose PSP data storage (Memory Stick):"), ALIGN_LEFT, false));
+ mainColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose where to keep PSP data (Memory Stick)"), ALIGN_LEFT, false));
+ mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 18.0f, 0.0f)));
+
+ ViewGroup *subColumns = new LinearLayoutList(ORIENT_HORIZONTAL);
+ mainColumn->Add(subColumns);
+
+ ViewGroup *leftColumn = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
+ subColumns->Add(leftColumn);
+
+ ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(220, FILL_PARENT, actionMenuMargins));
+ subColumns->Add(rightColumnItems);
// For legacy Android systems, so you can switch back to the old ways if you move to SD or something.
- // TODO: Gonna need a scroll view.
+ // Trying to avoid needing a scroll view, so only showing the explanation for one option at a time.
+
#if PPSSPP_PLATFORM(ANDROID)
if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
- leftColumn->Add(new Choice(iz->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnUseStorageRoot);
- leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true);
- leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true);
- leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true);
+ leftColumn->Add(new RadioButton(&choice_, CHOICE_STORAGE_ROOT, iz->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
+ if (choice_ == CHOICE_STORAGE_ROOT) {
+ AddExplanation(leftColumn, (MemStickScreen::Choice)choice_);
+ }
}
#endif
- // Let's only offer the browse-for-folder choice on Android 10 or later.
- // Earlier versions often don't really have working folder browsers.
- bool storageBrowserWorking = System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 29;
+ if (storageBrowserWorking_) {
+ //ImageID("I_FOLDER_OPEN")
+ leftColumn->Add(new RadioButton(&choice_, CHOICE_BROWSE_FOLDER, iz->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
- if (storageBrowserWorking) {
- leftColumn->Add(new Choice(iz->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnBrowse);
- leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true);
- leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true);
- leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true);
+ // TODO: Show current folder here if we have one set.
} else {
- leftColumn->Add(new Choice(iz->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnSetFolderManually);
+ leftColumn->Add(new RadioButton(&choice_, CHOICE_SET_MANUAL, iz->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true);
leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true);
+ // TODO: Show current folder here if we have one set.
+ }
+ if (choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) {
+ UI::View *extraView = nullptr;
+ if (!g_Config.memStickDirectory.empty()) {
+ extraView = new TextView(StringFromFormat(" %s: %s", iz->T("Current"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false);
+ }
+ AddExplanation(leftColumn, (MemStickScreen::Choice)choice_, extraView);
}
- leftColumn->Add(new Choice(iz->T("Use App Private Directory")))->OnClick.Handle(this, &MemStickScreen::OnUseInternalStorage);
- // Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic?
- leftColumn->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!")))->SetBullet(true);
- leftColumn->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!")))->SetBullet(true);
-#if GOLD
- leftColumn->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files")))->SetBullet(true);
-#else
- leftColumn->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files")))->SetBullet(true);
-#endif
+ std::string privateString = iz->T("Use App Private Data");
+ if (initialSetup_) {
+ privateString = StringFromFormat("%s (%s)", iz->T("Skip for now"), privateString.c_str());
+ }
+
+ leftColumn->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, privateString.c_str()))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
+ if (choice_ == CHOICE_PRIVATE_DIRECTORY) {
+ AddExplanation(leftColumn, (MemStickScreen::Choice)choice_);
+ }
leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
+ const char *confirmButtonText = nullptr;
+ ImageID confirmButtonImage = ImageID::invalid();
+ switch (choice_) {
+ case CHOICE_BROWSE_FOLDER:
+ confirmButtonText = di->T("OK");
+ confirmButtonImage = ImageID("I_FOLDER_OPEN");
+ break;
+ case CHOICE_PRIVATE_DIRECTORY:
+ if (initialSetup_) {
+ confirmButtonText = di->T("Skip");
+ confirmButtonImage = ImageID("I_WARNING");
+ } else {
+ confirmButtonText = di->T("OK");
+ }
+ break;
+ case CHOICE_STORAGE_ROOT:
+ case CHOICE_SET_MANUAL:
+ default:
+ confirmButtonText = di->T("OK");
+ break;
+ }
+
+ rightColumnItems->Add(new UI::Choice(confirmButtonText, confirmButtonImage))->OnClick.Handle(this, &MemStickScreen::OnConfirmClick);
+ rightColumnItems->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
+
if (!initialSetup_) {
- rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack);
+ rightColumnItems->Add(new UI::Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack);
+ }
+ if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_TV) {
+ rightColumnItems->Add(new UI::Choice(iz->T("WhatsThis", "What's this?")))->OnClick.Handle(this, &MemStickScreen::OnHelp);
}
INFO_LOG(SYSTEM, "MemStickScreen: initialSetup=%d", (int)initialSetup_);
}
-UI::EventReturn MemStickScreen::OnSetFolderManually(UI::EventParams ¶ms) {
+UI::EventReturn MemStickScreen::OnHelp(UI::EventParams ¶ms) {
+ LaunchBrowser("https://www.ppsspp.org/guide_storage.html");
+
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn MemStickScreen::OnChoiceClick(UI::EventParams ¶ms) {
+ // Change the confirm button to match the choice,
+ // and change the text that we show.
+ RecreateViews();
+
+ return UI::EVENT_DONE;
+}
+
+
+UI::EventReturn MemStickScreen::OnConfirmClick(UI::EventParams ¶ms) {
+ switch (choice_) {
+ case CHOICE_SET_MANUAL:
+ return SetFolderManually(params);
+ case CHOICE_STORAGE_ROOT:
+ return UseStorageRoot(params);
+ case CHOICE_PRIVATE_DIRECTORY:
+ return UseInternalStorage(params);
+ case CHOICE_BROWSE_FOLDER:
+ return Browse(params);
+ }
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn MemStickScreen::SetFolderManually(UI::EventParams ¶ms) {
// The old way, from before scoped storage.
#if PPSSPP_PLATFORM(ANDROID)
auto sy = GetI18NCategory("System");
@@ -248,7 +397,7 @@ UI::EventReturn MemStickScreen::OnSetFolderManually(UI::EventParams ¶ms) {
return UI::EVENT_DONE;
}
-UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) {
+UI::EventReturn MemStickScreen::UseInternalStorage(UI::EventParams ¶ms) {
Path pendingMemStickFolder = Path(g_extFilesDir);
if (initialSetup_) {
@@ -258,6 +407,7 @@ UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) {
TriggerFinish(DialogResult::DR_OK);
} else {
// This can't really happen?? Not worth making an error message.
+ ERROR_LOG_REPORT(SYSTEM, "Could not switch memstick path in setup (internal)");
}
} else if (pendingMemStickFolder != g_Config.memStickDirectory) {
// Always ask for confirmation when called from the UI. Likely there's already some data.
@@ -269,7 +419,7 @@ UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) {
return UI::EVENT_DONE;
}
-UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) {
+UI::EventReturn MemStickScreen::UseStorageRoot(UI::EventParams ¶ms) {
Path pendingMemStickFolder = Path(g_externalDir);
if (initialSetup_) {
@@ -279,6 +429,7 @@ UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) {
TriggerFinish(DialogResult::DR_OK);
} else {
// This can't really happen?? Not worth making an error message.
+ ERROR_LOG_REPORT(SYSTEM, "Could not switch memstick path in setup");
}
} else if (pendingMemStickFolder != g_Config.memStickDirectory) {
// Always ask for confirmation when called from the UI. Likely there's already some data.
@@ -290,7 +441,7 @@ UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) {
return UI::EVENT_DONE;
}
-UI::EventReturn MemStickScreen::OnBrowse(UI::EventParams ¶ms) {
+UI::EventReturn MemStickScreen::Browse(UI::EventParams ¶ms) {
System_SendMessage("browse_folder", "");
return UI::EVENT_DONE;
}
@@ -337,7 +488,14 @@ void MemStickScreen::update() {
}
}
-static bool ListFileSuffixesRecursively(const Path &root, Path folder, std::vector &dirSuffixes, std::vector &fileSuffixes) {
+// Keep the size with the file, so we can skip overly large ones in the move.
+// The user will have to take care of them afterwards, it'll just take too long probably.
+struct FileSuffix {
+ std::string suffix;
+ u64 fileSize;
+};
+
+static bool ListFileSuffixesRecursively(const Path &root, Path folder, std::vector &dirSuffixes, std::vector &fileSuffixes) {
std::vector files;
if (!File::GetFilesInDir(folder, &files)) {
return false;
@@ -350,12 +508,12 @@ static bool ListFileSuffixesRecursively(const Path &root, Path folder, std::vect
dirSuffixes.push_back(dirSuffix);
ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes);
} else {
- ERROR_LOG(SYSTEM, "Failed to compute PathTo from '%s' to '%s'", root.c_str(), folder.c_str());
+ ERROR_LOG_REPORT(SYSTEM, "Failed to compute PathTo from '%s' to '%s'", root.c_str(), folder.c_str());
}
} else {
std::string fileSuffix;
if (root.ComputePathTo(file.fullName, fileSuffix)) {
- fileSuffixes.push_back(fileSuffix);
+ fileSuffixes.push_back(FileSuffix{ fileSuffix, file.size });
}
}
}
@@ -401,17 +559,17 @@ void ConfirmMemstickMoveScreen::CreateViews() {
free_disk_space(newMemstickFolder_, freeSpaceNew);
free_disk_space(oldMemstickFolder, freeSpaceOld);
- leftColumn->Add(new TextView(iz->T("New PSP Data Folder"), ALIGN_LEFT, false));
+ leftColumn->Add(new TextView(iz->T("Selected PSP Data Folder"), ALIGN_LEFT, false));
if (!initialSetup_) {
- leftColumn->Add(new TextView(iz->T("PPSSPP will restart after the change."), ALIGN_LEFT, false));
+ leftColumn->Add(new TextView(iz->T("PPSSPP will restart after the change"), ALIGN_LEFT, false));
}
leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false));
std::string newFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceNew);
leftColumn->Add(new TextView(newFreeSpaceText, ALIGN_LEFT, false));
if (existingFilesInNewFolder_) {
- leftColumn->Add(new TextView(iz->T("Already contains data."), ALIGN_LEFT, false));
+ leftColumn->Add(new TextView(iz->T("Already contains PSP data"), ALIGN_LEFT, false));
if (!moveData_) {
- leftColumn->Add(new TextView(iz->T("No data will be changed."), ALIGN_LEFT, false));
+ leftColumn->Add(new TextView(iz->T("No data will be changed"), ALIGN_LEFT, false));
}
}
if (!error_.empty()) {
@@ -420,7 +578,7 @@ void ConfirmMemstickMoveScreen::CreateViews() {
if (!oldMemstickFolder.empty()) {
std::string oldFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceOld);
- rightColumn->Add(new TextView(iz->T("Old PSP Data Folder"), ALIGN_LEFT, false));
+ rightColumn->Add(new TextView(std::string(iz->T("Current")) + ":", ALIGN_LEFT, false));
rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false));
rightColumn->Add(new TextView(oldFreeSpaceText, ALIGN_LEFT, false));
}
@@ -455,15 +613,16 @@ void ConfirmMemstickMoveScreen::update() {
progressView_->SetText(progressReporter_.Get());
}
- bool *result = moveDataTask_->Poll();
+ MoveResult *result = moveDataTask_->Poll();
if (result) {
- if (*result) {
+ if (result->success) {
progressReporter_.Set(iz->T("Done!"));
INFO_LOG(SYSTEM, "Move data task finished successfully!");
// Succeeded!
FinishFolderMove();
} else {
+ progressReporter_.Set(iz->T("Failed to move some files!"));
INFO_LOG(SYSTEM, "Move data task failed!");
// What do we do here? We might be in the middle of a move... Bad.
RecreateViews();
@@ -474,7 +633,6 @@ void ConfirmMemstickMoveScreen::update() {
}
}
-
UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) {
auto sy = GetI18NCategory("System");
auto iz = GetI18NCategory("MemStick");
@@ -487,7 +645,7 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) {
if (moveData_) {
progressReporter_.Set(iz->T("Starting move..."));
- moveDataTask_ = Promise