mirror of
https://github.com/touchHLE/rust-sdl2.git
synced 2026-01-31 01:25:23 +01:00
Added system locale support (#1427)
* add system locale hint, query & event * update changelog * cleanup * fix multiple hint formatting, changelog * locale formatting optimization, additional formatting tests * Update changelog.md Co-authored-by: Antoni Spaanderman <49868160+antonilol@users.noreply.github.com> * remove redundant `Result` return type from `format_locale_hint` --------- Co-authored-by: Antoni Spaanderman <49868160+antonilol@users.noreply.github.com>
This commit is contained in:
@@ -27,6 +27,8 @@ when upgrading from a version of rust-sdl2 to another.
|
||||
|
||||
[PR #1407](https://github.com/Rust-SDL2/rust-sdl2/pull/1407) Add new use_ios_framework for linking to SDL2.framework on iOS
|
||||
|
||||
[PR #1427](https://github.com/Rust-SDL2/rust-sdl2/pull/1427) **BREAKING CHANGE** Add system locale support. A new event type `Event::LocaleChanged` has been added.
|
||||
|
||||
### v0.37.0
|
||||
|
||||
[PR #1406](https://github.com/Rust-SDL2/rust-sdl2/pull/1406) Update bindings to SDL 2.0.26, add Event.is\_touch() for mouse events, upgrade wgpu to 0.20 in examples
|
||||
|
||||
@@ -320,6 +320,8 @@ pub enum EventType {
|
||||
RenderTargetsReset = SDL_EventType::SDL_RENDER_TARGETS_RESET as u32,
|
||||
RenderDeviceReset = SDL_EventType::SDL_RENDER_DEVICE_RESET as u32,
|
||||
|
||||
LocaleChanged = SDL_EventType::SDL_LOCALECHANGED as u32,
|
||||
|
||||
User = SDL_EventType::SDL_USEREVENT as u32,
|
||||
Last = SDL_EventType::SDL_LASTEVENT as u32,
|
||||
}
|
||||
@@ -897,6 +899,10 @@ pub enum Event {
|
||||
timestamp: u32,
|
||||
},
|
||||
|
||||
LocaleChanged {
|
||||
timestamp: u32,
|
||||
},
|
||||
|
||||
User {
|
||||
timestamp: u32,
|
||||
window_id: u32,
|
||||
@@ -1975,6 +1981,10 @@ impl Event {
|
||||
timestamp: raw.common.timestamp,
|
||||
},
|
||||
|
||||
EventType::LocaleChanged => Event::LocaleChanged {
|
||||
timestamp: raw.common.timestamp,
|
||||
},
|
||||
|
||||
EventType::First => panic!("Unused event, EventType::First, was encountered"),
|
||||
EventType::Last => panic!("Unusable event, EventType::Last, was encountered"),
|
||||
|
||||
@@ -2180,6 +2190,7 @@ impl Event {
|
||||
Self::AudioDeviceRemoved { timestamp, .. } => timestamp,
|
||||
Self::RenderTargetsReset { timestamp, .. } => timestamp,
|
||||
Self::RenderDeviceReset { timestamp, .. } => timestamp,
|
||||
Self::LocaleChanged { timestamp, .. } => timestamp,
|
||||
Self::User { timestamp, .. } => timestamp,
|
||||
Self::Unknown { timestamp, .. } => timestamp,
|
||||
}
|
||||
@@ -2573,6 +2584,27 @@ impl Event {
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a locale event.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use sdl2::event::Event;
|
||||
///
|
||||
/// let ev = Event::LocaleChanged {
|
||||
/// timestamp: 0,
|
||||
/// };
|
||||
/// assert!(ev.is_locale());
|
||||
///
|
||||
/// let another_ev = Event::Quit {
|
||||
/// timestamp: 0,
|
||||
/// };
|
||||
/// assert!(another_ev.is_locale() == false); // Not a locale event!
|
||||
/// ```
|
||||
pub fn is_locale(&self) -> bool {
|
||||
matches!(self, Self::LocaleChanged { .. })
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a user event.
|
||||
///
|
||||
/// # Example
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::sys;
|
||||
use crate::{locale::Locale, sys};
|
||||
use libc::c_char;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
const VIDEO_MINIMIZE_ON_FOCUS_LOSS: &str = "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS";
|
||||
const PREFERRED_LOCALES: &str = "SDL_PREFERRED_LOCALES";
|
||||
|
||||
pub enum Hint {
|
||||
Default,
|
||||
@@ -74,6 +75,44 @@ pub fn get_video_minimize_on_focus_loss() -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// A hint that overrides the user's locale settings.
|
||||
///
|
||||
/// [Official SDL documentation](https://wiki.libsdl.org/SDL2/SDL_HINT_PREFERRED_LOCALES)
|
||||
///
|
||||
/// # Default
|
||||
/// This is disabled by default.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// See [`crate::locale::get_preferred_locales`].
|
||||
pub fn set_preferred_locales<T: std::borrow::Borrow<Locale>>(
|
||||
locales: impl IntoIterator<Item = T>,
|
||||
) -> bool {
|
||||
set(PREFERRED_LOCALES, &format_locale_hint(locales))
|
||||
}
|
||||
|
||||
fn format_locale_hint<T: std::borrow::Borrow<Locale>>(
|
||||
locales: impl IntoIterator<Item = T>,
|
||||
) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut iter = locales.into_iter();
|
||||
let (reserve, _) = iter.size_hint();
|
||||
// Assuming that most locales will be of the form "xx_yy",
|
||||
// plus 1 char for the comma.
|
||||
let mut formatted = String::with_capacity(reserve * 6);
|
||||
|
||||
if let Some(first) = iter.next() {
|
||||
write!(formatted, "{}", first.borrow()).ok();
|
||||
}
|
||||
|
||||
for locale in iter {
|
||||
write!(formatted, ",{}", locale.borrow()).ok();
|
||||
}
|
||||
|
||||
formatted
|
||||
}
|
||||
|
||||
#[doc(alias = "SDL_SetHint")]
|
||||
pub fn set(name: &str, value: &str) -> bool {
|
||||
let name = CString::new(name).unwrap();
|
||||
@@ -126,3 +165,52 @@ pub fn set_with_priority(name: &str, value: &str, priority: &Hint) -> bool {
|
||||
) == sys::SDL_bool::SDL_TRUE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn locale() {
|
||||
// Test set_preferred_locales
|
||||
let locales = [Locale {
|
||||
lang: "en".to_string(),
|
||||
country: Some("US".to_string()),
|
||||
}];
|
||||
set_preferred_locales(&locales);
|
||||
set_preferred_locales(locales);
|
||||
|
||||
// Test hint formatting
|
||||
assert_eq!(format_locale_hint(&[]), "");
|
||||
|
||||
assert_eq!(
|
||||
format_locale_hint([Locale {
|
||||
lang: "en".to_string(),
|
||||
country: None,
|
||||
}]),
|
||||
"en"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
format_locale_hint([Locale {
|
||||
lang: "en".to_string(),
|
||||
country: Some("US".to_string()),
|
||||
}]),
|
||||
"en_US"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
format_locale_hint([
|
||||
Locale {
|
||||
lang: "en".to_string(),
|
||||
country: Some("US".to_string()),
|
||||
},
|
||||
Locale {
|
||||
lang: "fr".to_string(),
|
||||
country: Some("FR".to_string()),
|
||||
},
|
||||
]),
|
||||
"en_US,fr_FR"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ pub mod haptic;
|
||||
pub mod hint;
|
||||
pub mod joystick;
|
||||
pub mod keyboard;
|
||||
pub mod locale;
|
||||
pub mod log;
|
||||
pub mod messagebox;
|
||||
pub mod mouse;
|
||||
|
||||
92
src/sdl2/locale.rs
Normal file
92
src/sdl2/locale.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
//! System locale information.
|
||||
|
||||
/// A locale defines a user's language and (optionally) region.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Locale {
|
||||
pub lang: String,
|
||||
pub country: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Locale {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.lang)?;
|
||||
|
||||
if let Some(region) = &self.country {
|
||||
write!(f, "_{}", region)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the user's preferred locales.
|
||||
///
|
||||
/// [Official SDL documentation](https://wiki.libsdl.org/SDL_GetPreferredLocales)
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let locales = [sdl2::locale::Locale {
|
||||
/// lang: "en".to_string(),
|
||||
/// country: Some("US".to_string()),
|
||||
/// }];
|
||||
///
|
||||
/// sdl2::hint::set_preferred_locales(&locales);
|
||||
///
|
||||
/// let preferred_locales = sdl2::locale::get_preferred_locales().collect::<Vec<_>>();
|
||||
/// assert_eq!(preferred_locales, locales);
|
||||
/// ```
|
||||
pub fn get_preferred_locales() -> LocaleIterator {
|
||||
unsafe {
|
||||
LocaleIterator {
|
||||
raw: sys::SDL_GetPreferredLocales(),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocaleIterator {
|
||||
raw: *mut sys::SDL_Locale,
|
||||
index: isize,
|
||||
}
|
||||
|
||||
impl Drop for LocaleIterator {
|
||||
fn drop(&mut self) {
|
||||
unsafe { sys::SDL_free(self.raw as *mut _) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for LocaleIterator {
|
||||
type Item = Locale;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let locale = unsafe { get_locale(self.raw.offset(self.index))? };
|
||||
self.index += 1;
|
||||
Some(locale)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_locale(ptr: *const sys::SDL_Locale) -> Option<Locale> {
|
||||
let sdl_locale = ptr.as_ref()?;
|
||||
|
||||
if sdl_locale.language.is_null() {
|
||||
return None;
|
||||
}
|
||||
let lang = std::ffi::CStr::from_ptr(sdl_locale.language)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
let region = try_get_string(sdl_locale.country);
|
||||
|
||||
Some(Locale {
|
||||
lang,
|
||||
country: region,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn try_get_string(ptr: *const i8) -> Option<String> {
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user