// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
suberror MappingError {
InvalidCode(Code)
InvalidName
NotImplemented
NotConnected
DuplicatedEntry
UnknownElement
NotSdl2Compatible
}
///|
let _mapping_error_keepalive : Array[MappingError] = [
MappingError::NotImplemented,
MappingError::NotConnected,
MappingError::DuplicatedEntry,
MappingError::NotSdl2Compatible,
]
///|
suberror ParseSdlMappingError {
UnknownHatDirection
ParseError(ParserError)
}
///|
pub struct Mapping {
mappings : Array[(Code, AxisOrBtn)]
mut name : String
default : Bool
mut hats_mapped : Int
}
///|
pub fn Mapping::new() -> Mapping {
{ mappings: [], name: "", default: false, hats_mapped: 0 }
}
///|
pub fn Mapping::new_default() -> Mapping {
{ mappings: [], name: "", default: true, hats_mapped: 0 }
}
///|
fn axis_same(a : Axis, b : Axis) -> Bool {
a.to_index() == b.to_index()
}
///|
fn button_same(a : Button, b : Button) -> Bool {
a.to_index() == b.to_index()
}
///|
fn axis_or_btn_same(a : AxisOrBtn, b : AxisOrBtn) -> Bool {
match a {
AxisOrBtn::Axis(ax) =>
match b {
AxisOrBtn::Axis(bx) => axis_same(ax, bx)
AxisOrBtn::Btn(_) => false
}
AxisOrBtn::Btn(btn) =>
match b {
AxisOrBtn::Axis(_) => false
AxisOrBtn::Btn(bn) => button_same(btn, bn)
}
}
}
///|
fn insert_mapping(
mappings : Array[(Code, AxisOrBtn)],
code : Code,
el : AxisOrBtn,
) -> Unit {
for i in 0.. Unit {
insert_mapping(self.mappings, code, el)
}
///|
pub fn Mapping::set_hats_mapped(self : Mapping, hats : Int) -> Unit {
self.hats_mapped = hats
}
///|
pub fn Mapping::map(self : Mapping, code : Code) -> AxisOrBtn? {
for pair in self.mappings {
let (c, v) = pair
if c == code {
return Some(v)
}
}
None
}
///|
pub fn Mapping::map_rev(self : Mapping, el : AxisOrBtn) -> Code? {
for pair in self.mappings {
let (c, v) = pair
if axis_or_btn_same(v, el) {
return Some(c)
}
}
None
}
///|
pub fn Mapping::name(self : Mapping) -> String {
self.name
}
///|
pub fn Mapping::is_default(self : Mapping) -> Bool {
self.default
}
///|
pub fn Mapping::hats_mapped(self : Mapping) -> Int {
self.hats_mapped
}
///|
pub fn Mapping::entries(self : Mapping) -> Array[(Code, AxisOrBtn)] {
self.mappings.copy()
}
///|
pub fn Mapping::parse_sdl_mapping(
line : String,
buttons : Array[Code],
axes : Array[Code],
) -> Mapping raise ParseSdlMappingError {
let mapping = Mapping::new()
let parser = Parser::new(line)
while true {
let res : Result[Token?, ParserError] = try? parser.next_token()
match res {
Ok(token_opt) =>
match token_opt {
None => break
Some(token) =>
match token {
Token::Uuid(_) => ()
Token::Platform(_) => ()
Token::Name(n) => mapping.name = n
Token::AxisMapping(idx, to, _, _, _) =>
if idx >= 0 && idx < axes.length() {
insert_mapping(mapping.mappings, axes[idx], to)
}
Token::ButtonMapping(idx, to, _) =>
if idx >= 0 && idx < buttons.length() {
insert_mapping(mapping.mappings, buttons[idx], to)
}
Token::HatMapping(hat, direction, to, _) => {
if hat != 0 {
continue
}
let (from_axis, from_btn) = match direction {
1 => (AXIS_DPADY, BTN_DPAD_UP)
4 => (AXIS_DPADY, BTN_DPAD_DOWN)
2 => (AXIS_DPADX, BTN_DPAD_RIGHT)
8 => (AXIS_DPADX, BTN_DPAD_LEFT)
0 => continue
_ => raise ParseSdlMappingError::UnknownHatDirection
}
if to.is_button() {
match to {
AxisOrBtn::Btn(Button::DPadLeft | Button::DPadRight) =>
insert_mapping(
mapping.mappings,
from_axis,
AxisOrBtn::Axis(Axis::DPadX),
)
AxisOrBtn::Btn(Button::DPadUp | Button::DPadDown) =>
insert_mapping(
mapping.mappings,
from_axis,
AxisOrBtn::Axis(Axis::DPadY),
)
_ => ()
}
insert_mapping(mapping.mappings, from_btn, to)
} else {
insert_mapping(mapping.mappings, from_axis, to)
}
mapping.hats_mapped = mapping.hats_mapped | direction
}
}
}
Err(e) =>
match e {
ParserError::Error(ParserErrorKind::EmptyValue, _) => continue
_ => raise ParseSdlMappingError::ParseError(e)
}
}
}
mapping
}
///|
pub struct MappingData {
buttons : Array[Code?]
axes : Array[Code?]
}
///|
pub fn MappingData::new() -> MappingData {
{
buttons: Array::make(BUTTON_COUNT, None),
axes: Array::make(AXIS_COUNT, None),
}
}
///|
pub fn MappingData::button(self : MappingData, idx : Button) -> Code? {
self.buttons[idx.to_index()]
}
///|
pub fn MappingData::axis(self : MappingData, idx : Axis) -> Code? {
self.axes[idx.to_index()]
}
///|
pub fn MappingData::insert_btn(
self : MappingData,
from : Code,
to : Button,
) -> Code? {
let i = to.to_index()
let prev = self.buttons[i]
self.buttons[i] = Some(from)
prev
}
///|
pub fn MappingData::insert_axis(
self : MappingData,
from : Code,
to : Axis,
) -> Code? {
let i = to.to_index()
let prev = self.axes[i]
self.axes[i] = Some(from)
prev
}
///|
pub fn MappingData::remove_button(self : MappingData, idx : Button) -> Code? {
let i = idx.to_index()
let prev = self.buttons[i]
self.buttons[i] = None
prev
}
///|
pub fn MappingData::remove_axis(self : MappingData, idx : Axis) -> Code? {
let i = idx.to_index()
let prev = self.axes[i]
self.axes[i] = None
prev
}
///|
fn is_name_valid(name : String) -> Bool {
for c in name {
if c == ',' {
return false
}
}
true
}
///|
fn index_of_code(list : Array[Code], code : Code) -> Int? {
for i in 0.. String raise MappingError {
let idx = match index_of_code(buttons, ev_code) {
Some(i) => i
None => raise MappingError::InvalidCode(ev_code)
}
insert_mapping(mappings, ev_code, AxisOrBtn::Btn(mapped_btn))
sdl_mappings + "\{ident}:b\{idx},"
}
///|
fn add_axis(
ident : String,
ev_code : Code,
mapped_axis : Axis,
axes : Array[Code],
sdl_mappings : String,
mappings : Array[(Code, AxisOrBtn)],
) -> String raise MappingError {
let idx = match index_of_code(axes, ev_code) {
Some(i) => i
None => raise MappingError::InvalidCode(ev_code)
}
insert_mapping(mappings, ev_code, AxisOrBtn::Axis(mapped_axis))
sdl_mappings + "\{ident}:a\{idx},"
}
///|
pub fn Mapping::from_data(
data : MappingData,
buttons : Array[Code],
axes : Array[Code],
name : String,
uuid : Uuid,
) -> (Mapping, String) raise MappingError {
if !is_name_valid(name) {
raise MappingError::InvalidName
}
if data.button(Button::Unknown) is Some(_) ||
data.axis(Axis::Unknown) is Some(_) {
raise MappingError::UnknownElement
}
let mappings : Array[(Code, AxisOrBtn)] = []
let mut sdl_mappings = "\{uuid.simple()},\{name},"
if data.axis(Axis::LeftStickX) is Some(code) {
sdl_mappings = add_axis(
"leftx",
code,
Axis::LeftStickX,
axes,
sdl_mappings,
mappings,
)
}
if data.axis(Axis::LeftStickY) is Some(code) {
sdl_mappings = add_axis(
"lefty",
code,
Axis::LeftStickY,
axes,
sdl_mappings,
mappings,
)
}
if data.axis(Axis::LeftZ) is Some(code) {
sdl_mappings = add_axis(
"leftz",
code,
Axis::LeftZ,
axes,
sdl_mappings,
mappings,
)
}
if data.axis(Axis::RightStickX) is Some(code) {
sdl_mappings = add_axis(
"rightx",
code,
Axis::RightStickX,
axes,
sdl_mappings,
mappings,
)
}
if data.axis(Axis::RightStickY) is Some(code) {
sdl_mappings = add_axis(
"righty",
code,
Axis::RightStickY,
axes,
sdl_mappings,
mappings,
)
}
if data.axis(Axis::RightZ) is Some(code) {
sdl_mappings = add_axis(
"rightz",
code,
Axis::RightZ,
axes,
sdl_mappings,
mappings,
)
}
if data.button(Button::South) is Some(code) {
sdl_mappings = add_button(
"a",
code,
Button::South,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::East) is Some(code) {
sdl_mappings = add_button(
"b",
code,
Button::East,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::West) is Some(code) {
sdl_mappings = add_button(
"x",
code,
Button::West,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::North) is Some(code) {
sdl_mappings = add_button(
"y",
code,
Button::North,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::LeftTrigger) is Some(code) {
sdl_mappings = add_button(
"leftshoulder",
code,
Button::LeftTrigger,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::RightTrigger) is Some(code) {
sdl_mappings = add_button(
"rightshoulder",
code,
Button::RightTrigger,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::LeftTrigger2) is Some(code) {
sdl_mappings = add_button(
"lefttrigger",
code,
Button::LeftTrigger2,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::RightTrigger2) is Some(code) {
sdl_mappings = add_button(
"righttrigger",
code,
Button::RightTrigger2,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::Select) is Some(code) {
sdl_mappings = add_button(
"back",
code,
Button::Select,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::Start) is Some(code) {
sdl_mappings = add_button(
"start",
code,
Button::Start,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::Mode) is Some(code) {
sdl_mappings = add_button(
"guide",
code,
Button::Mode,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::LeftThumb) is Some(code) {
sdl_mappings = add_button(
"leftstick",
code,
Button::LeftThumb,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::RightThumb) is Some(code) {
sdl_mappings = add_button(
"rightstick",
code,
Button::RightThumb,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::DPadUp) is Some(code) {
sdl_mappings = add_button(
"dpup",
code,
Button::DPadUp,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::DPadDown) is Some(code) {
sdl_mappings = add_button(
"dpdown",
code,
Button::DPadDown,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::DPadLeft) is Some(code) {
sdl_mappings = add_button(
"dpleft",
code,
Button::DPadLeft,
buttons,
sdl_mappings,
mappings,
)
}
if data.button(Button::DPadRight) is Some(code) {
sdl_mappings = add_button(
"dpright",
code,
Button::DPadRight,
buttons,
sdl_mappings,
mappings,
)
}
({ mappings, name, default: false, hats_mapped: 0 }, sdl_mappings)
}
///|
pub struct MappingDb {
mappings : Array[(Uuid, String)]
}
///|
const INCLUDED_MAPPINGS_FALLBACK : String = "030000005e0400008e02000002010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,"
///|
pub fn MappingDb::new() -> MappingDb {
{ mappings: [] }
}
///|
fn index_of_substring(s : String, pat : String) -> Int? {
let n = s.length()
let m = pat.length()
if m == 0 {
return Some(0)
}
if n < m {
return None
}
for i in 0..<(n - m + 1) {
let mut ok = true
for j in 0.. Int? {
for i in 0.. String? {
match index_of_substring(line, "platform:") {
None => None
Some(idx) => {
let start = idx + "platform:".length()
if start >= line.length() {
return Some("")
}
let tail = (try! line[start:line.length()]).to_string()
let end = match find_first_comma(tail) {
None => tail.length()
Some(i) => i
}
Some((try! tail[0:end]).to_string())
}
}
}
///|
fn mapping_line_matches_platform(line : String) -> Bool {
let current = runtime_sdl_platform_name()
match mapping_line_platform(line) {
None => true
Some(p) => p == current
}
}
///|
fn upsert_mapping(db : MappingDb, uuid : Uuid, line : String) -> Unit {
for i in 0.. Unit {
for line_view in s.split("\n") {
let line = line_view.to_string()
if line.length() == 0 {
continue
}
if !mapping_line_matches_platform(line) {
continue
}
match find_first_comma(line) {
None => continue
Some(i) => {
let guid = (try! line[0:i]).to_string()
try {
let uuid = Uuid::parse(guid)
upsert_mapping(self, uuid, line)
} catch {
_ => ()
}
}
}
}
}
///|
pub fn MappingDb::add_included_mappings(self : MappingDb) -> Unit {
self.insert(INCLUDED_MAPPINGS_FALLBACK)
if runtime_sdl_platform_name() == "Mac OS X" {
self.insert(included_mappings_macos_blob())
}
}
///|
pub fn MappingDb::add_env_mappings(self : MappingDb) -> Unit {
let env = runtime_env_sdl_gamecontrollerconfig()
if env.length() != 0 {
self.insert(env)
}
}
///|
pub fn MappingDb::get(self : MappingDb, uuid : Uuid) -> String? {
for pair in self.mappings {
let (u, v) = pair
if u.simple() == uuid.simple() {
return Some(v)
}
}
None
}
///|
pub fn MappingDb::len(self : MappingDb) -> Int {
self.mappings.length()
}