diff --git a/include/libremidi/api-c.h b/include/libremidi/api-c.h index db5f26a..8822253 100644 --- a/include/libremidi/api-c.h +++ b/include/libremidi/api-c.h @@ -30,6 +30,8 @@ typedef enum libremidi_api WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */ KEYBOARD_UMP, /*!< Computer keyboard input */ NETWORK_UMP, /*!< MIDI2 over IP */ + JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */ + PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */ DUMMY = 0xFFFF /*!< A compilable but non-functional API. */ } libremidi_api; diff --git a/include/libremidi/api.hpp b/include/libremidi/api.hpp index 208fd9d..44add71 100644 --- a/include/libremidi/api.hpp +++ b/include/libremidi/api.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -31,9 +30,6 @@ LIBREMIDI_EXPORT std::vector available_apis() noexcept; */ LIBREMIDI_EXPORT std::vector available_ump_apis() noexcept; -LIBREMIDI_EXPORT -libremidi::API midi_api(const std::any& conf); - //! A static function to determine the current version. LIBREMIDI_EXPORT std::string_view get_version() noexcept; @@ -64,7 +60,7 @@ inline constexpr libremidi::API default_api() noexcept return API::COREMIDI; #elif defined(_WIN32) return API::WINDOWS_MM; -#elif defined(__linux__) +#elif defined(LIBREMIDI_ALSA) return API::ALSA_SEQ; #elif defined(__emscripten__) return API::EMSCRIPTEN_WEBMIDI; @@ -83,7 +79,7 @@ inline constexpr libremidi::API default_api() noexcept return API::COREMIDI_UMP; #elif defined(_WIN32) return API::WINDOWS_MIDI_SERVICES; -#elif defined(__linux__) +#elif defined(LIBREMIDI_ALSA) return API::ALSA_SEQ_UMP; #elif defined(__emscripten__) return API::DUMMY; diff --git a/include/libremidi/backends.hpp b/include/libremidi/backends.hpp index fb289eb..18c42a1 100644 --- a/include/libremidi/backends.hpp +++ b/include/libremidi/backends.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #if !__has_include() && !__has_include() @@ -30,10 +29,14 @@ #if defined(LIBREMIDI_JACK) #include + #include #endif #if defined(LIBREMIDI_PIPEWIRE) #include + #if defined(LIBREMIDI_PIPEWIRE_UMP) + #include + #endif #endif #if defined(LIBREMIDI_COREMIDI) @@ -163,6 +166,14 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_NETWORK) , net_ump::backend{} +#endif +#if defined(LIBREMIDI_JACK) + , + jack_ump::backend{} +#endif +#if defined(LIBREMIDI_PIPEWIRE_UMP) + , + pipewire_ump::backend{} #endif , dummy_backend{}); @@ -173,7 +184,7 @@ static_assert(std::tuple_size_v >= 1); template auto for_all_backends(F&& f) { - std::apply([&](auto&&... x) { (f(x), ...); }, available_backends); + std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends); } template @@ -202,10 +213,10 @@ auto for_backend(libremidi::API api, F&& f) midi2::for_backend(api, f); } -void for_input_configuration(auto f, std::any& api_conf) +void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = std::get_if(&api_conf)) { f(*conf); return true; @@ -217,10 +228,10 @@ void for_input_configuration(auto f, std::any& api_conf) std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); } -void for_output_configuration(auto f, std::any& api_conf) +void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = std::get_if(&api_conf)) { f(*conf); return true; @@ -232,10 +243,10 @@ void for_output_configuration(auto f, std::any& api_conf) std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); } -void for_observer_configuration(auto f, std::any& api_conf) +void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = std::get_if(&api_conf)) { f(*conf); return true; diff --git a/include/libremidi/backends/alsa_raw.hpp b/include/libremidi/backends/alsa_raw.hpp index 06556b6..e80b3d2 100644 --- a/include/libremidi/backends/alsa_raw.hpp +++ b/include/libremidi/backends/alsa_raw.hpp @@ -3,6 +3,7 @@ #include #include +#include // Credits: greatly inspired from // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c @@ -19,8 +20,8 @@ struct backend using midi_out_configuration = alsa_raw_output_configuration; using midi_observer_configuration = alsa_raw_observer_configuration; static const constexpr auto API = libremidi::API::ALSA_RAW; - static const constexpr auto name = "alsa_raw"; - static const constexpr auto display_name = "ALSA (raw)"; + static const constexpr std::string_view name = "alsa_raw"; + static const constexpr std::string_view display_name = "ALSA (raw)"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/alsa_raw/config.hpp b/include/libremidi/backends/alsa_raw/config.hpp index 446ce9b..ace8343 100644 --- a/include/libremidi/backends/alsa_raw/config.hpp +++ b/include/libremidi/backends/alsa_raw/config.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/include/libremidi/backends/alsa_raw/helpers.hpp b/include/libremidi/backends/alsa_raw/helpers.hpp index 1783a5a..3eb955f 100644 --- a/include/libremidi/backends/alsa_raw/helpers.hpp +++ b/include/libremidi/backends/alsa_raw/helpers.hpp @@ -2,10 +2,17 @@ #include #include #include +#include -#include +#include + +#include +#include +#include +#include #include #include +#include #include // Credits: greatly inspired from @@ -27,14 +34,14 @@ struct alsa_raw_port_id }; inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept { - return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port; + return (uint64_t(id.port) << 32) + (uint64_t(id.dev) << 16) + id.card; } inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept { alsa_raw_port_id ret; - ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32; + ret.port = (p & 0x00'00'FF'FF'00'00'00'00) >> 32; ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16; - ret.port = (p & 0x00'00'00'00'00'00'FF'FF); + ret.card = (p & 0x00'00'00'00'00'00'FF'FF); return ret; } static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102); diff --git a/include/libremidi/backends/alsa_raw/midi_in.hpp b/include/libremidi/backends/alsa_raw/midi_in.hpp index fda1b36..22449b8 100644 --- a/include/libremidi/backends/alsa_raw/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw/midi_in.hpp @@ -200,7 +200,7 @@ public: midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -220,7 +220,7 @@ public: private: void run_thread(auto parse_func) { - fds_.push_back(this->termination_event); + fds_.push_back(this->m_termination_event); const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); @@ -228,12 +228,12 @@ private: for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), period); + ssize_t err = poll(fds_.data(), fds_.size(), static_cast(period)); if (err == -EAGAIN) continue; else if (err < 0) return; - else if (termination_event.ready(fds_.back())) + else if (m_termination_event.ready(fds_.back())) break; err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); @@ -250,11 +250,11 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; + this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; } else { - this->thread_ = std::thread{ + this->m_thread = std::thread{ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; } return stdx::error{}; @@ -282,16 +282,16 @@ private: stdx::error close_port() override { - termination_event.notify(); - if (thread_.joinable()) - thread_.join(); - termination_event.consume(); // Reset to zero + m_termination_event.notify(); + if (m_thread.joinable()) + m_thread.join(); + m_termination_event.consume(); // Reset to zero return midi_in_impl::close_port(); } - std::thread thread_; - eventfd_notifier termination_event{}; + std::thread m_thread; + eventfd_notifier m_termination_event{}; }; class midi_in_alsa_raw_manual : public midi_in_impl diff --git a/include/libremidi/backends/alsa_raw/observer.hpp b/include/libremidi/backends/alsa_raw/observer.hpp index 68f2afb..f7559b5 100644 --- a/include/libremidi/backends/alsa_raw/observer.hpp +++ b/include/libremidi/backends/alsa_raw/observer.hpp @@ -6,7 +6,8 @@ #if LIBREMIDI_HAS_UDEV #include #include - #include +#endif +#include namespace libremidi::alsa_raw { @@ -31,24 +32,30 @@ public: if (!configuration.has_callbacks()) return; - fds[0] = this->udev; - fds[1] = termination_event; - fds[2] = timer_fd; +#if LIBREMIDI_HAS_UDEV + m_fds[0] = this->m_udev; + m_fds[1] = m_termination_event; + m_fds[2] = m_timer_fd; +#endif // Set-up initial state if (configuration.notify_in_constructor) this->check_devices(); +#if LIBREMIDI_HAS_UDEV // Start thread - thread = std::thread{[this] { this->run(); }}; + m_thread = std::thread{[this] { this->run(); }}; +#endif } ~observer_impl_base() { - termination_event.notify(); +#if LIBREMIDI_HAS_UDEV + m_termination_event.notify(); - if (thread.joinable()) - thread.join(); + if (m_thread.joinable()) + m_thread.join(); +#endif } std::vector get_input_ports() const noexcept override @@ -78,11 +85,12 @@ public: } private: +#if LIBREMIDI_HAS_UDEV void run() { for (;;) { - if (int err = poll(fds, 3, -1); err < 0) + if (int err = poll(m_fds, 3, -1); err < 0) { if (err == -EAGAIN) continue; @@ -91,58 +99,77 @@ private: } // Check udev - if (fds[0].revents & POLLIN) + if (m_fds[0].revents & POLLIN) { - udev_device* dev = udev.udev.monitor_receive_device(udev.monitor); + udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor); if (!dev) continue; - std::string_view act = udev.udev.device_get_action(dev); - std::string_view ss = udev.udev.device_get_subsystem(dev); + std::string_view act = m_udev.udev.device_get_action(dev); + std::string_view ss = m_udev.udev.device_get_subsystem(dev); if (!act.empty() && ss == "snd_seq") { if (act == "add" || act == "remove") { // Check every 100 milliseconds for ten seconds - this->timer_fd.restart(configuration.poll_period.count()); - timer_check_counts = 100; + this->m_timer_fd.restart(configuration.poll_period.count()); + m_timer_check_counts = 100; } } - udev.udev.device_unref(dev); + m_udev.udev.device_unref(dev); - fds[0].revents = 0; + m_fds[0].revents = 0; } // Check eventfd - if (fds[1].revents & POLLIN) + if (m_fds[1].revents & POLLIN) { break; } // Check timer - if (fds[2].revents & POLLIN) + if (m_fds[2].revents & POLLIN) { - if (this->timer_check_counts-- <= 0) - this->timer_fd.cancel(); - fds[2].revents = 0; + if (this->m_timer_check_counts-- <= 0) + this->m_timer_fd.cancel(); + m_fds[2].revents = 0; check_devices(); } } } +#endif template - auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept + auto to_port_info(const alsa_raw::alsa_raw_port_info& p) const noexcept -> std::conditional_t { +#if LIBREMIDI_HAS_UDEV + auto [container, device, type] = get_udev_soundcard_info(m_udev, p.card); +#else + container_identifier container{}; + device_identifier device{}; + libremidi::port_information::port_type type{}; +#endif + std::string display_name = p.subdevice_name; + if (display_name.empty()) + display_name = p.device_name; + if (display_name.empty()) + display_name = p.card_name; + if (display_name.empty()) + display_name = "unknown"; + return { {.client = 0, + .container = container, + .device = device, .port = raw_to_port_handle({p.card, p.dev, p.sub}), .manufacturer = p.card_name, .device_name = p.device_name, .port_name = p.subdevice_name, - .display_name = p.subdevice_name}}; + .display_name = std::move(display_name), + .type = type}}; } void check_devices() @@ -151,7 +178,7 @@ private: new_devs.enumerate_cards(); - for (auto& in_prev : current_inputs) + for (auto& in_prev : m_current_inputs) { if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev); it == new_devs.inputs.end()) @@ -165,8 +192,8 @@ private: for (auto& in_next : new_devs.inputs) { - if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next); - it == current_inputs.end()) + if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next); + it == m_current_inputs.end()) { if (auto& cb = this->configuration.input_added) { @@ -175,7 +202,7 @@ private: } } - for (auto& out_prev : current_outputs) + for (auto& out_prev : m_current_outputs) { if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev); it == new_devs.outputs.end()) @@ -189,8 +216,8 @@ private: for (auto& out_next : new_devs.outputs) { - if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next); - it == current_outputs.end()) + if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next); + it == m_current_outputs.end()) { if (auto& cb = this->configuration.output_added) { @@ -198,38 +225,24 @@ private: } } } - current_inputs = std::move(new_devs.inputs); - current_outputs = std::move(new_devs.outputs); + m_current_inputs = std::move(new_devs.inputs); + m_current_outputs = std::move(new_devs.outputs); } - udev_helper udev{}; - eventfd_notifier termination_event{}; - timerfd_timer timer_fd{}; - int timer_check_counts = 0; - std::thread thread; - std::vector current_inputs; - std::vector current_outputs; - - pollfd fds[3]{}; -}; -} -#else - #include -namespace libremidi::alsa_raw -{ -template -struct observer_impl_base : observer_dummy -{ - explicit observer_impl_base( - [[maybe_unused]] observer_configuration&& conf, - [[maybe_unused]] alsa_raw_observer_configuration&& apiconf) - : observer_dummy{dummy_configuration{}, dummy_configuration{}} - { - } -}; -} +#if LIBREMIDI_HAS_UDEV + udev_helper m_udev{}; + eventfd_notifier m_termination_event{}; + timerfd_timer m_timer_fd{}; + int m_timer_check_counts = 0; + std::thread m_thread; + pollfd m_fds[3]{}; #endif + std::vector m_current_inputs; + std::vector m_current_outputs; +}; +} + namespace libremidi::alsa_raw { struct observer_impl : observer_impl_base diff --git a/include/libremidi/backends/alsa_raw_ump.hpp b/include/libremidi/backends/alsa_raw_ump.hpp index 3fc7adf..a6316e7 100644 --- a/include/libremidi/backends/alsa_raw_ump.hpp +++ b/include/libremidi/backends/alsa_raw_ump.hpp @@ -5,6 +5,8 @@ #include #include +#include + namespace libremidi::alsa_raw_ump { struct backend @@ -14,12 +16,11 @@ struct backend using midi_observer = observer_impl; using midi_in_configuration = alsa_raw_ump::input_configuration; using midi_out_configuration = alsa_raw_ump::output_configuration; - struct midi_observer_configuration : alsa_raw_observer_configuration - { - }; + using midi_observer_configuration = alsa_raw_ump::observer_configuration; + static const constexpr auto API = libremidi::API::ALSA_RAW_UMP; - static const constexpr auto name = "alsa_raw_ump"; - static const constexpr auto display_name = "ALSA (raw UMP)"; + static const constexpr std::string_view name = "alsa_raw_ump"; + static const constexpr std::string_view display_name = "ALSA (raw UMP)"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/alsa_raw_ump/config.hpp b/include/libremidi/backends/alsa_raw_ump/config.hpp index a89ccaf..39c71b6 100644 --- a/include/libremidi/backends/alsa_raw_ump/config.hpp +++ b/include/libremidi/backends/alsa_raw_ump/config.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include +#include + namespace libremidi::alsa_raw_ump { struct input_configuration @@ -18,8 +21,7 @@ struct output_configuration std::optional chunking; }; -struct observer_configuration +struct observer_configuration : public alsa_raw_observer_configuration { - std::chrono::milliseconds poll_period{100}; }; } diff --git a/include/libremidi/backends/alsa_raw_ump/helpers.hpp b/include/libremidi/backends/alsa_raw_ump/helpers.hpp index 5b16ca9..6c22ed5 100644 --- a/include/libremidi/backends/alsa_raw_ump/helpers.hpp +++ b/include/libremidi/backends/alsa_raw_ump/helpers.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace libremidi::alsa_raw_ump { struct midi2_enumerator : alsa_raw::enumerator diff --git a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp index e4b1640..0403d29 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp @@ -194,7 +194,7 @@ public: libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -206,14 +206,14 @@ public: ~midi_in_impl_threaded() { // Close a connection if it exists. - this->close_port(); + midi_in_impl_threaded::close_port(); client_open_ = std::errc::not_connected; } private: void run_thread(auto parse_func) { - fds_.push_back(this->termination_event); + fds_.push_back(this->m_termination_event); const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); @@ -221,12 +221,12 @@ private: for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), period); + ssize_t err = poll(fds_.data(), fds_.size(), static_cast(period)); if (err == -EAGAIN) continue; else if (err < 0) return; - else if (termination_event.ready(fds_.back())) + else if (m_termination_event.ready(fds_.back())) break; err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); @@ -243,11 +243,11 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; + this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; } else { - this->thread_ = std::thread{ + this->m_thread = std::thread{ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; } return stdx::error{}; @@ -275,16 +275,16 @@ private: stdx::error close_port() override { - termination_event.notify(); - if (thread_.joinable()) - thread_.join(); - termination_event.consume(); // Reset to zero + m_termination_event.notify(); + if (m_thread.joinable()) + m_thread.join(); + m_termination_event.consume(); // Reset to zero return midi_in_impl::close_port(); } - std::thread thread_; - eventfd_notifier termination_event{}; + std::thread m_thread; + eventfd_notifier m_termination_event{}; }; class midi_in_impl_manual : public midi_in_impl @@ -299,7 +299,7 @@ public: ~midi_in_impl_manual() { // Close a connection if it exists. - this->close_port(); + midi_in_impl_manual::close_port(); client_open_ = std::errc::not_connected; } diff --git a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp index f350cde..26c7155 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp @@ -1,12 +1,17 @@ #pragma once #include #include +#include #include +#include +#include #include -#include -#include +#include +#include +#include +#include namespace libremidi::alsa_raw_ump { diff --git a/include/libremidi/backends/alsa_seq.hpp b/include/libremidi/backends/alsa_seq.hpp index 0d2c43b..ccbc40b 100644 --- a/include/libremidi/backends/alsa_seq.hpp +++ b/include/libremidi/backends/alsa_seq.hpp @@ -16,6 +16,10 @@ #include #include +#include + +#include + namespace libremidi::alsa_seq { @@ -29,13 +33,16 @@ struct backend using midi_out_configuration = alsa_seq::output_configuration; using midi_observer_configuration = alsa_seq::observer_configuration; static const constexpr auto API = libremidi::API::ALSA_SEQ; - static const constexpr auto name = "alsa_seq"; - static const constexpr auto display_name = "ALSA (sequencer)"; + static const constexpr std::string_view name = "alsa_seq"; + static const constexpr std::string_view display_name = "ALSA (sequencer)"; static inline bool available() noexcept { static const libasound& snd = libasound::instance(); - return snd.available && snd.seq.available; + if (!snd.available || !snd.seq.available) + return false; + + return ::access("/dev/snd/seq", F_OK) == 0; } }; diff --git a/include/libremidi/backends/alsa_seq/helpers.hpp b/include/libremidi/backends/alsa_seq/helpers.hpp index 0500a28..5ca667c 100644 --- a/include/libremidi/backends/alsa_seq/helpers.hpp +++ b/include/libremidi/backends/alsa_seq/helpers.hpp @@ -46,19 +46,19 @@ namespace { inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept { - return (client << 32) + port; + return (port << 32) + client; } inline constexpr std::pair seq_from_port_handle(port_handle p) noexcept { - int client = p >> 32; - int port = p & 0xFFFFFFFF; + int port = p >> 32; + int client = p & 0xFFFFFFFF; return {client, port}; } inline void for_all_ports( const libasound& snd, snd_seq_t* seq, - std::function func) + const std::function& func) { snd_seq_client_info_t* cinfo{}; snd_seq_client_info_alloca(&cinfo); diff --git a/include/libremidi/backends/alsa_seq/midi_in.hpp b/include/libremidi/backends/alsa_seq/midi_in.hpp index 8065097..c232c6c 100644 --- a/include/libremidi/backends/alsa_seq/midi_in.hpp +++ b/include/libremidi/backends/alsa_seq/midi_in.hpp @@ -212,7 +212,7 @@ public: .count(); } - int process_event(const snd_seq_event_t& ev) + int64_t process_event(const snd_seq_event_t& ev) { if constexpr (ConfigurationImpl::midi_version == 1) { @@ -258,17 +258,17 @@ public: return 0; } - int process_events() + int64_t process_events() { if constexpr (ConfigurationImpl::midi_version == 1) { snd_seq_event_t* ev{}; event_handle handle{snd}; - int result = 0; + int64_t result = 0; if ((result = snd.seq.event_input(seq, &ev)) > 0) { handle.reset(ev); - if (int err = process_event(*ev); err < 0) + if (auto err = process_event(*ev); err < 0) { return err; } @@ -353,7 +353,7 @@ public: midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { this->libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -364,7 +364,7 @@ public: ~midi_in_alsa_threaded() { - this->close_port(); + midi_in_alsa_threaded::close_port(); this->client_open_ = std::errc::not_connected; } @@ -399,7 +399,7 @@ private: { try { - this->thread = std::thread([this] { thread_handler(); }); + this->m_thread = std::thread([this] { thread_handler(); }); return stdx::error{}; } catch (const std::system_error& e) @@ -415,12 +415,12 @@ private: stdx::error stop_thread() { - termination_event.notify(); + m_termination_event.notify(); - if (this->thread.joinable()) - this->thread.join(); + if (this->m_thread.joinable()) + this->m_thread.join(); - termination_event.consume(); + m_termination_event.consume(); return stdx::error{}; } @@ -428,7 +428,7 @@ private: { int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1; auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd)); - poll_fds[0] = this->termination_event; + poll_fds[0] = this->m_termination_event; alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN); const auto period @@ -439,10 +439,10 @@ private: if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0) { // No data pending - if (poll(poll_fds, poll_fd_count, period) >= 0) + if (poll(poll_fds, poll_fd_count, static_cast(period)) >= 0) { // We got our stop-thread signal - if (termination_event.ready(poll_fds[0])) + if (m_termination_event.ready(poll_fds[0])) { break; } @@ -450,7 +450,7 @@ private: continue; } - int res{}; + int64_t res{}; if constexpr (ConfigurationImpl::midi_version == 1) { res = this->process_events(); @@ -467,8 +467,8 @@ private: } } - std::thread thread{}; - eventfd_notifier termination_event{}; + std::thread m_thread{}; + eventfd_notifier m_termination_event{}; }; template @@ -497,7 +497,7 @@ public: return 0; } - ~midi_in_alsa_manual() { this->close_port(); } + ~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); } stdx::error open_port(const input_port& pt, std::string_view local_port_name) override { diff --git a/include/libremidi/backends/alsa_seq/midi_out.hpp b/include/libremidi/backends/alsa_seq/midi_out.hpp index 0eea4ba..aaa91cf 100644 --- a/include/libremidi/backends/alsa_seq/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq/midi_out.hpp @@ -30,7 +30,7 @@ public: return; } - if (snd.midi.event_new(this->bufferSize, &this->coder) < 0) + if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0) { libremidi_handle_error(this->configuration, "error initializing MIDI event parser."); return; @@ -68,8 +68,9 @@ public: stdx::error open_port(const output_port& p, std::string_view portName) override { - unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); - if (nSrc < 1) + unsigned int n_src + = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); + if (n_src < 1) { libremidi_handle_error(this->configuration, "no MIDI output sources found!"); return make_error_code(std::errc::no_such_device); @@ -122,9 +123,9 @@ public: stdx::error send_message(const unsigned char* message, std::size_t size) override { int64_t result{}; - if (size > this->bufferSize) + if (size > this->m_bufferSize) { - this->bufferSize = size; + this->m_bufferSize = size; result = snd.midi.event_resize_buffer(this->coder, size); if (result != 0) { @@ -146,8 +147,8 @@ public: // FIXME direct is set but snd_seq_event_output_direct is not used... snd_seq_ev_set_direct(&ev); - const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below - result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev); + const int64_t n_bytes = size; // signed to avoir potential overflow with size - offset below + result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - offset), &ev); if (result < 0) { libremidi_handle_warning(this->configuration, "event parsing error!"); @@ -174,6 +175,6 @@ public: } private: - uint64_t bufferSize{32}; + uint64_t m_bufferSize{32}; }; } diff --git a/include/libremidi/backends/alsa_seq/observer.hpp b/include/libremidi/backends/alsa_seq/observer.hpp index fb101cb..41261a6 100644 --- a/include/libremidi/backends/alsa_seq/observer.hpp +++ b/include/libremidi/backends/alsa_seq/observer.hpp @@ -7,7 +7,10 @@ #include -#include +#if LIBREMIDI_HAS_UDEV + #include +#endif + #include namespace libremidi::alsa_seq @@ -21,6 +24,8 @@ struct port_info int port{}; bool isInput{}; bool isOutput{}; + std::optional card{}; + libremidi::port_information::port_type type{}; }; template @@ -107,13 +112,19 @@ public: const auto tp = snd.seq.port_info_get_type(pinfo); bool ok = this->configuration.track_any; - static constexpr auto virtual_port - = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER; + static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER + | SND_SEQ_PORT_TYPE_APPLICATION; if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) + { + p.type = libremidi::port_information::port_type::hardware; ok = true; + } else if ((tp & virtual_port) && this->configuration.track_virtual) + { + p.type = libremidi::port_information::port_type::software; ok = true; + } if (!ok) return {}; @@ -123,7 +134,10 @@ public: if (auto name = snd.seq.port_info_get_name(pinfo)) p.port_name = name; - auto cap = snd.seq.port_info_get_capability(pinfo); + if (int card = snd.seq.client_info_get_card(cinfo); card >= 0) + p.card = card; + + const auto cap = snd.seq.port_info_get_capability(pinfo); p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); @@ -131,18 +145,34 @@ public: } template - auto to_port_info(port_info p) const noexcept + auto to_port_info(const port_info& p) const noexcept -> std::conditional_t { static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle)); static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle)); + + container_identifier container{}; + device_identifier device{}; + libremidi::port_information::port_type type = p.type; +#if LIBREMIDI_HAS_UDEV + if (p.card) + { + auto res = get_udev_soundcard_info(m_udev, *p.card); + container = res.container; + device = res.path; + type = res.type; + } +#endif return { {.client = std::uintptr_t(this->seq), + .container = container, + .device = device, .port = alsa_seq::seq_to_port_handle(p.client, p.port), .manufacturer = "", .device_name = p.client_name, .port_name = p.port_name, - .display_name = p.port_name}}; + .display_name = p.port_name, + .type = type}}; } void init_all_ports() @@ -200,7 +230,7 @@ public: if (p.client == snd.seq.client_id(seq)) return; - knownClients_[{p.client, p.port}] = p; + m_knownClients[{p.client, p.port}] = p; if (p.isInput && configuration.input_added) { configuration.input_added(to_port_info(p)); @@ -214,11 +244,11 @@ public: void unregister_port(int client, int port) { - auto it = knownClients_.find({client, port}); - if (it != knownClients_.end()) + auto it = m_knownClients.find({client, port}); + if (it != m_knownClients.end()) { auto p = it->second; - knownClients_.erase(it); + m_knownClients.erase(it); if (p.isInput && configuration.input_removed) { @@ -263,7 +293,11 @@ public: } private: - std::map, port_info> knownClients_; + std::map, port_info> m_knownClients; + +#if LIBREMIDI_HAS_UDEV + udev_helper m_udev{}; +#endif }; template @@ -275,9 +309,9 @@ public: { // Create relevant descriptors auto& snd = alsa_data::snd; - const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN); - descriptors_.resize(N + 1); - snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN); + const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN); + descriptors_.resize(n + 1); + snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN); descriptors_.back() = this->termination_event; // Start the listening thread @@ -288,7 +322,7 @@ public: .count(); for (;;) { - int err = poll(descriptors_.data(), descriptors_.size(), period); + int err = poll(descriptors_.data(), descriptors_.size(), static_cast(period)); if (err >= 0) { // We got our stop-thread signal diff --git a/include/libremidi/backends/alsa_seq/shared_handler.hpp b/include/libremidi/backends/alsa_seq/shared_handler.hpp index f9434a0..580344c 100644 --- a/include/libremidi/backends/alsa_seq/shared_handler.hpp +++ b/include/libremidi/backends/alsa_seq/shared_handler.hpp @@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context { auto clt = std::make_shared(client_name); - auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) { + auto cb = [client = std::weak_ptr{clt}](libremidi::alsa_seq::poll_parameters params) { if (auto clt = client.lock()) { clt->events.push( @@ -74,8 +74,7 @@ struct shared_handler : public libremidi::shared_context auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) { if (auto clt = client.lock()) { - clt->events.push( - {.type = shared_handler::event_type::callback_removed, .payload = std::move(id)}); + clt->events.push({.type = shared_handler::event_type::callback_removed, .payload = id}); clt->queue_event.notify(); } return true; @@ -134,7 +133,7 @@ struct shared_handler : public libremidi::shared_context break; } case callback_removed: - auto addr = std::move(*std::get_if(&ev.payload)); + auto addr = *std::get_if(&ev.payload); if (auto index = index_of_address(addr); index >= 0) { addresses.erase(addresses.begin() + index); @@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context } // Look for who's ready - for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++) + for (int64_t i = 0, n = std::ssize(fds) - 2; i < n; i++) { if (fds[i].revents & POLLIN) { // Read alsa event snd_seq_event_t* ev{}; event_handle handle{snd}; - int result = 0; - while ((result = snd.seq.event_input(client, &ev)) > 0) + while (snd.seq.event_input(client, &ev) > 0) { handle.reset(ev); diff --git a/include/libremidi/backends/alsa_seq_ump.hpp b/include/libremidi/backends/alsa_seq_ump.hpp index ae91f79..b9f83ff 100644 --- a/include/libremidi/backends/alsa_seq_ump.hpp +++ b/include/libremidi/backends/alsa_seq_ump.hpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + namespace libremidi { template <> @@ -50,13 +54,16 @@ struct backend using midi_out_configuration = alsa_seq_ump::output_configuration; using midi_observer_configuration = alsa_seq_ump::observer_configuration; static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP; - static const constexpr auto name = "alsa_seq_ump"; - static const constexpr auto display_name = "ALSA (sequencer, UMP)"; + static const constexpr std::string_view name = "alsa_seq_ump"; + static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)"; static inline bool available() noexcept { static const libasound& snd = libasound::instance(); - return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available; + if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available) + return false; + + return ::access("/dev/snd/seq", F_OK) == 0; } }; } diff --git a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp index 46760d6..3cfb063 100644 --- a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp @@ -64,8 +64,9 @@ public: stdx::error open_port(const output_port& p, std::string_view portName) override { - unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); - if (nSrc < 1) + unsigned int n_src + = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); + if (n_src < 1) { libremidi_handle_error(this->configuration, "no MIDI output sources found!"); return make_error_code(std::errc::no_such_device); @@ -133,7 +134,8 @@ public: libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return static_cast(-ret); } - return std::errc{0}; + static_assert(std::errc{0} == std::errc{}); + return std::errc{}; }; segment_ump_stream(ump_stream, count, write_func, []() {}); diff --git a/include/libremidi/backends/coremidi.hpp b/include/libremidi/backends/coremidi.hpp index e6f2ee4..86fd485 100644 --- a/include/libremidi/backends/coremidi.hpp +++ b/include/libremidi/backends/coremidi.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace libremidi { struct core_backend @@ -14,8 +16,8 @@ struct core_backend using midi_out_configuration = coremidi_output_configuration; using midi_observer_configuration = coremidi_observer_configuration; static const constexpr auto API = libremidi::API::COREMIDI; - static const constexpr auto name = "core"; - static const constexpr auto display_name = "CoreMIDI"; + static const constexpr std::string_view name = "core"; + static const constexpr std::string_view display_name = "CoreMIDI"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/coremidi/helpers.hpp b/include/libremidi/backends/coremidi/helpers.hpp index 30f5eea..07e0913 100644 --- a/include/libremidi/backends/coremidi/helpers.hpp +++ b/include/libremidi/backends/coremidi/helpers.hpp @@ -71,20 +71,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime) return static_cast(res); } #endif + +static inline auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name) +{ + CFStringRef str = nullptr; + MIDIObjectGetStringProperty(prop, name, &str); + return CFString_handle{str}; +}; + // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) { CFMutableStringRef result = CFStringCreateMutable(nullptr, 0); - static constexpr auto getProp = [](MIDIObjectRef prop) { - CFStringRef str = nullptr; - MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str); - return CFString_handle{str}; - }; - // Begin with the endpoint's name. - if (auto endpoint_name = getProp(endpoint)) + if (auto endpoint_name = get_cfstring_property(endpoint, kMIDIPropertyName)) { CFStringAppend(result, endpoint_name.get()); } @@ -101,7 +103,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) if (CFStringGetLength(result) == 0) { // endpoint name has zero length -- try the entity - if (auto entity_name = getProp(entity)) + if (auto entity_name = get_cfstring_property(entity, kMIDIPropertyName)) { CFStringAppend(result, entity_name.get()); } @@ -112,7 +114,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) if (device == 0) goto finish; - if (auto dev_name = getProp(device)) + if (auto dev_name = get_cfstring_property(device, kMIDIPropertyName)) { const auto dev_strlen = CFStringGetLength(dev_name.get()); diff --git a/include/libremidi/backends/coremidi/observer.hpp b/include/libremidi/backends/coremidi/observer.hpp index 833be51..4f422d2 100644 --- a/include/libremidi/backends/coremidi/observer.hpp +++ b/include/libremidi/backends/coremidi/observer.hpp @@ -78,13 +78,55 @@ public: if (!ok) return {}; + // Get the MIDI device from the entity + libremidi::port_information::port_type type{}; + libremidi::container_identifier usb_location_id{}; + libremidi::device_identifier usb_vendor_product{}; + { + MIDIDeviceRef device{}; + if (MIDIEntityGetDevice(e, &device) == noErr) + { + if (device) + { + using enum libremidi::port_information::port_type; + SInt32 devid_res{}; + if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr) + { + type = (libremidi::port_information::port_type)(hardware | usb); + usb_location_id = (uint64_t)devid_res; + } + + SInt32 vendor_res{}; + if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res) + == noErr) + { + type = (libremidi::port_information::port_type)(hardware | usb); + usb_vendor_product = (uint64_t)vendor_res; + } + + const auto driver = get_string_property(device, kMIDIPropertyDriverOwner); + if (driver == "com.apple.AppleMIDIUSBDriver") + type = (libremidi::port_information::port_type)(hardware | usb); + if (driver == "com.apple.AppleMIDIBluetoothDriver") + type = (libremidi::port_information::port_type)(hardware | bluetooth); + if (driver == "com.apple.AppleMIDIIACDriver") + type = (libremidi::port_information::port_type)(software); + if (driver == "com.apple.AppleMIDIRTPDriver") + type = (libremidi::port_information::port_type)(network); + } + } + } + return std::conditional_t{ {.client = (std::uintptr_t)this->client, + .container = usb_location_id, + .device = usb_vendor_product, .port = std::bit_cast(get_int_property(obj, kMIDIPropertyUniqueID)), .manufacturer = get_string_property(obj, kMIDIPropertyManufacturer), .device_name = get_string_property(obj, kMIDIPropertyModel), .port_name = get_string_property(obj, kMIDIPropertyName), - .display_name = get_string_property(obj, kMIDIPropertyDisplayName)}}; + .display_name = get_string_property(obj, kMIDIPropertyDisplayName), + .type = type}}; } std::vector get_input_ports() const noexcept override diff --git a/include/libremidi/backends/coremidi_ump.hpp b/include/libremidi/backends/coremidi_ump.hpp index 67f7728..49b5da9 100644 --- a/include/libremidi/backends/coremidi_ump.hpp +++ b/include/libremidi/backends/coremidi_ump.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace libremidi::coremidi_ump { struct backend @@ -14,8 +16,8 @@ struct backend using midi_out_configuration = coremidi_ump::output_configuration; using midi_observer_configuration = coremidi_ump::observer_configuration; static const constexpr auto API = libremidi::API::COREMIDI_UMP; - static const constexpr auto name = "core_ump"; - static const constexpr auto display_name = "CoreMIDI UMP"; + static const constexpr std::string_view name = "core_ump"; + static const constexpr std::string_view display_name = "CoreMIDI UMP"; static constexpr inline bool available() noexcept { return true; /* todo? */ } }; diff --git a/include/libremidi/backends/dummy.hpp b/include/libremidi/backends/dummy.hpp index b3fb029..971e2ed 100644 --- a/include/libremidi/backends/dummy.hpp +++ b/include/libremidi/backends/dummy.hpp @@ -5,6 +5,8 @@ #include #include +#include + namespace libremidi { class observer_dummy : public observer_api @@ -23,11 +25,12 @@ class midi_in_dummy final , public error_handler { public: - explicit midi_in_dummy(const input_configuration& configuration, std::any) + explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } - explicit midi_in_dummy(const ump_input_configuration& configuration, std::any) + explicit midi_in_dummy( + const ump_input_configuration& configuration, const input_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } @@ -58,7 +61,8 @@ class midi_out_dummy final , public error_handler { public: - explicit midi_out_dummy(const output_configuration& configuration, std::any) + explicit midi_out_dummy( + const output_configuration& configuration, const output_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } @@ -97,8 +101,8 @@ struct dummy_backend using midi_out_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration; static const constexpr auto API = libremidi::API::DUMMY; - static const constexpr auto name = "dummy"; - static const constexpr auto display_name = "Dummy"; + static const constexpr std::string_view name = "dummy"; + static const constexpr std::string_view display_name = "Dummy"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/emscripten.hpp b/include/libremidi/backends/emscripten.hpp index 867e943..ccd7dd2 100644 --- a/include/libremidi/backends/emscripten.hpp +++ b/include/libremidi/backends/emscripten.hpp @@ -7,6 +7,8 @@ #include #include + #include + namespace libremidi { @@ -19,8 +21,8 @@ struct emscripten_backend using midi_out_configuration = emscripten_output_configuration; using midi_observer_configuration = emscripten_observer_configuration; static const constexpr auto API = libremidi::API::WEBMIDI; - static const constexpr auto name = "webmidi"; - static const constexpr auto display_name = "WebMIDI"; + static const constexpr std::string_view name = "webmidi"; + static const constexpr std::string_view display_name = "WebMIDI"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/emscripten/midi_in.cpp b/include/libremidi/backends/emscripten/midi_in.cpp index 3b919d3..7b37356 100644 --- a/include/libremidi/backends/emscripten/midi_in.cpp +++ b/include/libremidi/backends/emscripten/midi_in.cpp @@ -38,7 +38,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std:: } midi.open_input(portNumber, *this); - portNumber_ = portNumber; + m_portNumber = portNumber; return stdx::error{}; } @@ -52,7 +52,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port() { auto& midi = webmidi_helpers::midi_access_emscripten::instance(); - midi.close_input(portNumber_, *this); + midi.close_input(m_portNumber, *this); return stdx::error{}; } diff --git a/include/libremidi/backends/emscripten/midi_in.hpp b/include/libremidi/backends/emscripten/midi_in.hpp index 4833903..feacd37 100644 --- a/include/libremidi/backends/emscripten/midi_in.hpp +++ b/include/libremidi/backends/emscripten/midi_in.hpp @@ -31,7 +31,7 @@ public: void on_input(double ts, unsigned char* begin, unsigned char* end); private: - int portNumber_{}; + int m_portNumber{}; midi1::input_state_machine m_processing{this->configuration}; }; diff --git a/include/libremidi/backends/emscripten/midi_out.cpp b/include/libremidi/backends/emscripten/midi_out.cpp index e2bf024..0fd89cf 100644 --- a/include/libremidi/backends/emscripten/midi_out.cpp +++ b/include/libremidi/backends/emscripten/midi_out.cpp @@ -34,7 +34,7 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std: return std::errc::invalid_argument; } - portNumber_ = portNumber; + m_portNumber = portNumber; return stdx::error{}; } @@ -49,14 +49,14 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() { LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size) { - if (portNumber_ < 0) + if (m_portNumber < 0) libremidi_handle_error( this->configuration, "trying to send a message without an open " "port."); webmidi_helpers::midi_access_emscripten::instance().send_message( - portNumber_, reinterpret_cast(message), size); + m_portNumber, reinterpret_cast(message), size); return stdx::error{}; } } diff --git a/include/libremidi/backends/emscripten/midi_out.hpp b/include/libremidi/backends/emscripten/midi_out.hpp index f6f2137..a62849d 100644 --- a/include/libremidi/backends/emscripten/midi_out.hpp +++ b/include/libremidi/backends/emscripten/midi_out.hpp @@ -28,6 +28,6 @@ public: stdx::error send_message(const unsigned char* message, size_t size) override; private: - int portNumber_{-1}; + int m_portNumber{-1}; }; } diff --git a/include/libremidi/backends/jack.hpp b/include/libremidi/backends/jack.hpp index 8367676..7b52368 100644 --- a/include/libremidi/backends/jack.hpp +++ b/include/libremidi/backends/jack.hpp @@ -3,6 +3,8 @@ #include #include +#include + //*********************************************************************// // API: UNIX JACK // @@ -22,8 +24,8 @@ struct jack_backend using midi_out_configuration = jack_output_configuration; using midi_observer_configuration = jack_observer_configuration; static const constexpr auto API = libremidi::API::JACK_MIDI; - static const constexpr auto name = "jack"; - static const constexpr auto display_name = "JACK"; + static const constexpr std::string_view name = "jack"; + static const constexpr std::string_view display_name = "JACK"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/jack/helpers.hpp b/include/libremidi/backends/jack/helpers.hpp index 60eee90..4811ca5 100644 --- a/include/libremidi/backends/jack/helpers.hpp +++ b/include/libremidi/backends/jack/helpers.hpp @@ -5,7 +5,9 @@ #include #include -#include +#include +#include +#include namespace libremidi { @@ -54,16 +56,16 @@ struct jack_client } template - static auto - get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept - -> std::vector> + static auto get_ports( + jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags, + bool midi2) noexcept -> std::vector> { std::vector> ret; if (!client) return {}; - const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags); + const char** ports = jack_get_ports(client, pattern, type, flags); if (ports == nullptr) return {}; @@ -73,7 +75,11 @@ struct jack_client { // FIXME this does not take into account filtering sw / hw ports auto port = jack_port_by_name(client, ports[i]); - ret.push_back(to_port_info(client, port)); + if (port) + { + if (bool(midi2) == bool(jack_port_flags(port) & 0x20)) + ret.push_back(to_port_info(client, port)); + } i++; } @@ -132,7 +138,7 @@ struct jack_helpers : jack_client {.token = this_instance, .callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int { if (auto pt = p.lock()) - if (auto ppt = pt->load()) + if (pt->load()) self.process(nf); self.thread_lock.check_client_released(); @@ -192,8 +198,8 @@ struct jack_helpers : jack_client self.client_open_ = std::errc::not_connected; } - stdx::error - create_local_port(const auto& self, std::string_view portName, JackPortFlags flags) + stdx::error create_local_port( + const auto& self, std::string_view portName, const char* type, JackPortFlags flags) { // full name: "client_name:port_name\0" if (portName.empty()) @@ -209,8 +215,7 @@ struct jack_helpers : jack_client if (!this->port) { - this->port - = jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0); + this->port = jack_port_register(this->client, portName.data(), type, flags, 0); } if (!this->port) @@ -238,4 +243,77 @@ struct jack_helpers : jack_client return from_errc(err); } }; + +struct jack_queue +{ +public: + static constexpr auto size_sz = sizeof(int32_t); + + jack_queue() = default; + jack_queue(const jack_queue&) = delete; + jack_queue(jack_queue&&) = delete; + jack_queue& operator=(const jack_queue&) = delete; + + jack_queue& operator=(jack_queue&& other) noexcept + { + ringbuffer = other.ringbuffer; + ringbuffer_space = other.ringbuffer_space; + other.ringbuffer = nullptr; + return *this; + } + + explicit jack_queue(int64_t sz) noexcept + { + ringbuffer = jack_ringbuffer_create(sz); + ringbuffer_space = jack_ringbuffer_write_space(ringbuffer); + } + + ~jack_queue() noexcept + { + if (ringbuffer) + jack_ringbuffer_free(ringbuffer); + } + + stdx::error write(const unsigned char* data, int64_t sz) const noexcept + { + if (static_cast(sz + size_sz) > ringbuffer_space) + return std::errc::no_buffer_space; + + while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) + sched_yield(); + + jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); + jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); + + return stdx::error{}; + } + + void read(void* jack_events) const noexcept + { + int32_t sz; + while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast(&sz), size_sz) == size_sz + && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz) + { + jack_ringbuffer_read_advance(ringbuffer, size_sz); + + if (auto midi = jack_midi_event_reserve(jack_events, 0, sz)) + jack_ringbuffer_read(ringbuffer, reinterpret_cast(midi), sz); + else + jack_ringbuffer_read_advance(ringbuffer, sz); + } + } + + jack_ringbuffer_t* ringbuffer{}; + std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer +}; + +struct jack_midi1 +{ + static constexpr const char* port_type = "8 bit raw midi"; +}; + +struct jack_midi2 +{ + static constexpr const char* port_type = "32 bit raw UMP"; +}; } diff --git a/include/libremidi/backends/jack/midi_in.hpp b/include/libremidi/backends/jack/midi_in.hpp index c51b9b6..f49e582 100644 --- a/include/libremidi/backends/jack/midi_in.hpp +++ b/include/libremidi/backends/jack/midi_in.hpp @@ -4,13 +4,12 @@ #include #include -#include - namespace libremidi { class midi_in_jack final : public midi1::in_api , public jack_helpers + , public jack_midi1 , public error_handler { public: @@ -46,7 +45,8 @@ public: stdx::error open_port(const input_port& port, std::string_view portName) override { - if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{}) + if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput); + err != stdx::error{}) return err; if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); @@ -62,7 +62,7 @@ public: stdx::error open_virtual_port(std::string_view portName) override { - return create_local_port(*this, portName, JackPortIsInput); + return create_local_port(*this, portName, port_type, JackPortIsInput); } stdx::error close_port() override { return do_close_port(); } @@ -96,8 +96,8 @@ public: this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); // We have midi events in buffer - uint32_t evCount = jack_midi_get_event_count(buff); - for (uint32_t j = 0; j < evCount; j++) + uint32_t ev_count = jack_midi_get_event_count(buff); + for (uint32_t j = 0; j < ev_count; j++) { jack_midi_event_t event{}; jack_midi_event_get(&event, buff, j); diff --git a/include/libremidi/backends/jack/midi_out.hpp b/include/libremidi/backends/jack/midi_out.hpp index 7077541..544662a 100644 --- a/include/libremidi/backends/jack/midi_out.hpp +++ b/include/libremidi/backends/jack/midi_out.hpp @@ -3,76 +3,12 @@ #include #include -#include - namespace libremidi { -struct jack_queue -{ -public: - static constexpr auto size_sz = sizeof(int32_t); - - jack_queue() = default; - jack_queue(const jack_queue&) = delete; - jack_queue(jack_queue&&) = delete; - jack_queue& operator=(const jack_queue&) = delete; - - jack_queue& operator=(jack_queue&& other) noexcept - { - ringbuffer = other.ringbuffer; - ringbuffer_space = other.ringbuffer_space; - other.ringbuffer = nullptr; - return *this; - } - - explicit jack_queue(int64_t sz) noexcept - { - ringbuffer = jack_ringbuffer_create(sz); - ringbuffer_space = jack_ringbuffer_write_space(ringbuffer); - } - - ~jack_queue() noexcept - { - if (ringbuffer) - jack_ringbuffer_free(ringbuffer); - } - - stdx::error write(const unsigned char* data, int64_t sz) const noexcept - { - if (static_cast(sz + size_sz) > ringbuffer_space) - return std::errc::no_buffer_space; - - while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) - sched_yield(); - - jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); - jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); - - return stdx::error{}; - } - - void read(void* jack_events) const noexcept - { - int32_t sz; - while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast(&sz), size_sz) == size_sz - && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz) - { - jack_ringbuffer_read_advance(ringbuffer, size_sz); - - if (auto midi = jack_midi_event_reserve(jack_events, 0, sz)) - jack_ringbuffer_read(ringbuffer, reinterpret_cast(midi), sz); - else - jack_ringbuffer_read_advance(ringbuffer, sz); - } - } - - jack_ringbuffer_t* ringbuffer{}; - std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer -}; - class midi_out_jack : public midi1::out_api , public jack_helpers + , public jack_midi1 , public error_handler { public: @@ -94,7 +30,8 @@ public: stdx::error open_port(const output_port& port, std::string_view portName) override { - if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{}) + if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput); + err != stdx::error{}) return err; // Connecting to the output @@ -111,7 +48,7 @@ public: stdx::error open_virtual_port(std::string_view portName) override { - return create_local_port(*this, portName, JackPortIsOutput); + return create_local_port(*this, portName, port_type, JackPortIsOutput); } stdx::error close_port() override { return do_close_port(); } @@ -128,7 +65,7 @@ class midi_out_jack_queued final : public midi_out_jack public: midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf) : midi_out_jack{std::move(conf), std::move(apiconf)} - , queue{configuration.ringbuffer_size} + , m_queue{configuration.ringbuffer_size} { auto status = connect(*this); if (!this->client) @@ -150,7 +87,7 @@ public: stdx::error send_message(const unsigned char* message, std::size_t size) override { - return queue.write(message, size); + return m_queue.write(message, size); } int process(jack_nframes_t nframes) @@ -158,13 +95,13 @@ public: void* buff = jack_port_get_buffer(this->port, nframes); jack_midi_clear_buffer(buff); - this->queue.read(buff); + this->m_queue.read(buff); return 0; } private: - jack_queue queue; + jack_queue m_queue; }; class midi_out_jack_direct final : public midi_out_jack diff --git a/include/libremidi/backends/jack/observer.hpp b/include/libremidi/backends/jack/observer.hpp index 01226b1..f1467e2 100644 --- a/include/libremidi/backends/jack/observer.hpp +++ b/include/libremidi/backends/jack/observer.hpp @@ -10,6 +10,7 @@ namespace libremidi class observer_jack final : public observer_api , private jack_client + , public jack_midi1 , private error_handler { public: @@ -48,8 +49,7 @@ public: void initial_callback() { { - const char** ports - = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput); + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput); if (ports != nullptr) { @@ -80,8 +80,7 @@ public: } { - const char** ports - = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput); if (ports != nullptr) { @@ -119,7 +118,7 @@ public: if (reg) { std::string_view type = jack_port_type(port); - if (type != JACK_DEFAULT_MIDI_TYPE) + if (type != port_type) return; bool physical = flags & JackPortIsPhysical; @@ -199,12 +198,12 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports(this->client, nullptr, JackPortIsOutput); + return get_ports(this->client, nullptr, port_type, JackPortIsOutput, false); } std::vector get_output_ports() const noexcept override { - return get_ports(this->client, nullptr, JackPortIsInput); + return get_ports(this->client, nullptr, port_type, JackPortIsInput, false); } ~observer_jack() diff --git a/include/libremidi/backends/jack_ump.hpp b/include/libremidi/backends/jack_ump.hpp new file mode 100644 index 0000000..2246c24 --- /dev/null +++ b/include/libremidi/backends/jack_ump.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include + +#include + +namespace libremidi::jack_ump +{ +struct backend +{ + using midi_in = midi_in_jack; + using midi_out = midi_out_jack; + using midi_observer = observer_jack; + using midi_in_configuration = jack_ump::input_configuration; + using midi_out_configuration = jack_ump::output_configuration; + using midi_observer_configuration = jack_ump::observer_configuration; + static const constexpr auto API = libremidi::API::JACK_UMP; + static const constexpr std::string_view name = "jack_ump"; + static const constexpr std::string_view display_name = "JACK (UMP)"; + + static inline bool available() noexcept + { +#if LIBREMIDI_WEAKJACK + if (WeakJack::instance().available() != 0) + return false; +#endif + + int major, minor, micro, patch; + jack_get_version(&major, &minor, µ, &patch); + return (major >= 3 && minor >= 1 && micro >= 4); + } +}; +} diff --git a/include/libremidi/backends/jack_ump/config.hpp b/include/libremidi/backends/jack_ump/config.hpp new file mode 100644 index 0000000..e828e25 --- /dev/null +++ b/include/libremidi/backends/jack_ump/config.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +namespace libremidi::jack_ump +{ +struct input_configuration +{ + std::string client_name = "libremidi client"; + + jack_client_t* context{}; + std::function set_process_func{}; + std::function clear_process_func{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + + jack_client_t* context{}; + std::function set_process_func{}; + std::function clear_process_func{}; + + int32_t ringbuffer_size = 16384; + bool direct = false; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + jack_client_t* context{}; +}; + +} diff --git a/include/libremidi/backends/jack_ump/midi_in.hpp b/include/libremidi/backends/jack_ump/midi_in.hpp new file mode 100644 index 0000000..7aa6087 --- /dev/null +++ b/include/libremidi/backends/jack_ump/midi_in.hpp @@ -0,0 +1,119 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi::jack_ump +{ +class midi_in_jack final + : public midi2::in_api + , public jack_helpers + , private jack_midi1 + , public error_handler +{ +public: + using midi_api::client_open_; + struct + : libremidi::ump_input_configuration + , jack_ump::input_configuration + { + } configuration; + + explicit midi_in_jack( + libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + client_open_ = stdx::error{}; + } + + ~midi_in_jack() override + { + midi_in_jack::close_port(); + + disconnect(*this); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + stdx::error open_port(const input_port& port, std::string_view portName) override + { + if (auto err + = create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20)); + err != stdx::error{}) + return err; + + if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); + err != 0 && err != EEXIST) + { + libremidi_handle_error( + configuration, + "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); + return from_errc(err); + } + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view portName) override + { + return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20)); + } + + stdx::error close_port() override { return do_close_port(); } + + stdx::error set_port_name(std::string_view portName) override + { + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); + } + + timestamp absolute_timestamp() const noexcept override + { + return 1000 * jack_frames_to_time(client, jack_frame_time(client)); + } + + int process(jack_nframes_t nframes) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = true, + .has_samples = true, + }; + void* buff = jack_port_get_buffer(this->port, nframes); + + // Timing + jack_nframes_t current_frames{}; + jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC + jack_time_t next_usecs{}; + float period_usecs{}; + jack_get_cycle_times( + this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); + + // We have midi events in buffer + uint32_t ev_count = jack_midi_get_event_count(buff); + for (uint32_t j = 0; j < ev_count; j++) + { + jack_midi_event_t event{}; + jack_midi_event_get(&event, buff, j); + const auto to_ns + = [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); }; + + m_processing.on_bytes( + {(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)}, + m_processing.timestamp(to_ns, event.time)); + } + + return 0; + } + + midi2::input_state_machine m_processing{this->configuration}; +}; +} diff --git a/include/libremidi/backends/jack_ump/midi_out.hpp b/include/libremidi/backends/jack_ump/midi_out.hpp new file mode 100644 index 0000000..643070b --- /dev/null +++ b/include/libremidi/backends/jack_ump/midi_out.hpp @@ -0,0 +1,184 @@ +#pragma once +#include +#include +#include + +namespace libremidi::jack_ump +{ +class midi_out_jack + : public midi2::out_api + , public jack_helpers + , private jack_midi1 + , public error_handler +{ +public: + using midi_api::client_open_; + struct + : libremidi::output_configuration + , jack_ump::output_configuration + { + } configuration; + + midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + } + + ~midi_out_jack() override { } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + stdx::error open_port(const output_port& port, std::string_view portName) override + { + if (auto err + = create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20)); + err != stdx::error{}) + return err; + + // Connecting to the output + if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); + err != 0 && err != EEXIST) + { + libremidi_handle_error(configuration, "could not connect to port" + port.port_name); + return from_errc(err); + } + + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view portName) override + { + return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20)); + } + + stdx::error close_port() override { return do_close_port(); } + + stdx::error set_port_name(std::string_view portName) override + { + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); + } +}; + +class midi_out_jack_queued final : public midi_out_jack +{ +public: + midi_out_jack_queued( + libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : midi_out_jack{std::move(conf), std::move(apiconf)} + , m_queue{configuration.ringbuffer_size} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + client_open_ = stdx::error{}; + } + + ~midi_out_jack_queued() override + { + midi_out_jack::close_port(); + + disconnect(*this); + } + + stdx::error send_ump(const uint32_t* message, std::size_t size) override + { + return m_queue.write((unsigned char*)message, size * sizeof(uint32_t)); + } + + int process(jack_nframes_t nframes) + { + void* buff = jack_port_get_buffer(this->port, nframes); + jack_midi_clear_buffer(buff); + + this->m_queue.read(buff); + + return 0; + } + +private: + libremidi::jack_queue m_queue; +}; + +class midi_out_jack_direct final : public midi_out_jack +{ +public: + midi_out_jack_direct( + libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : midi_out_jack{std::move(conf), std::move(apiconf)} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + buffer_size = jack_get_buffer_size(this->client); + client_open_ = stdx::error{}; + } + + ~midi_out_jack_direct() override + { + midi_out_jack::close_port(); + + disconnect(*this); + } + + int process(jack_nframes_t nframes) + { + void* buff = jack_port_get_buffer(this->port, nframes); + jack_midi_clear_buffer(buff); + return 0; + } + + stdx::error send_ump(const uint32_t* message, std::size_t size) override + { + void* buff = jack_port_get_buffer(this->port, buffer_size); + int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t)); + return from_errc(ret); + } + + int convert_timestamp(int64_t user) const noexcept + { + switch (configuration.timestamps) + { + case timestamp_mode::AudioFrame: + return static_cast(user); + + default: + // TODO + return 0; + } + } + + stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override + { + void* buff = jack_port_get_buffer(this->port, buffer_size); + int ret = jack_midi_event_write( + buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t)); + return from_errc(ret); + } + + int buffer_size{}; +}; +} + +namespace libremidi +{ +template <> +inline std::unique_ptr +make(output_configuration&& conf, jack_ump::output_configuration&& api) +{ + if (api.direct) + return std::make_unique(std::move(conf), std::move(api)); + else + return std::make_unique(std::move(conf), std::move(api)); +} +} diff --git a/include/libremidi/backends/jack_ump/observer.hpp b/include/libremidi/backends/jack_ump/observer.hpp new file mode 100644 index 0000000..699166a --- /dev/null +++ b/include/libremidi/backends/jack_ump/observer.hpp @@ -0,0 +1,235 @@ +#pragma once +#include +#include +#include + +#include + +namespace libremidi::jack_ump +{ +class observer_jack final + : public observer_api + , private jack_client + , private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used + , private error_handler +{ +public: + struct + : libremidi::observer_configuration + , jack_ump::observer_configuration + { + } configuration; + + explicit observer_jack( + libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + // Initialize JACK client + if (configuration.context) + { + this->client = configuration.context; + set_callbacks(); + } + else + { + jack_status_t status{}; + this->client + = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); + if (status != jack_status_t{}) + libremidi_handle_error(configuration, std::to_string((int)status)); + + if (this->client != nullptr) + { + set_callbacks(); + + jack_activate(this->client); + } + } + } + + void initial_callback() + { + { + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput); + + if (ports != nullptr) + { + int i = 0; + while (ports[i] != nullptr) + { + const auto port = jack_port_by_name(client, ports[i]); + const auto flags = jack_port_flags(port); + + if (!(flags & 0x20)) // midi 2 check + { + i++; + continue; + } + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + + if (ok) + { + seen_input_ports.insert(ports[i]); + if (this->configuration.input_added && configuration.notify_in_constructor) + this->configuration.input_added(to_port_info(client, port)); + } + i++; + } + } + + jack_free(ports); + } + + { + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput); + + if (ports != nullptr) + { + int i = 0; + while (ports[i] != nullptr) + { + const auto port = jack_port_by_name(client, ports[i]); + const auto flags = jack_port_flags(port); + + if (!(flags & 0x20)) // midi 2 check + { + i++; + continue; + } + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + + if (ok) + { + seen_output_ports.insert(ports[i]); + if (this->configuration.output_added && configuration.notify_in_constructor) + this->configuration.output_added(to_port_info(client, port)); + } + i++; + } + } + + jack_free(ports); + } + } + + void on_port_callback(jack_port_t* port, bool reg) + { + auto flags = jack_port_flags(port); + std::string name = jack_port_name(port); + if (reg) + { + std::string_view type = jack_port_type(port); + if (type != port_type) + return; + + if (!(flags & 0x20)) // midi 2 check + return; + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + if (!ok) + return; + + // Note: we keep track of the ports as + // when disconnecting, jack_port_type and jack_port_flags aren't correctly + // set anymore. + + if (flags & JackPortIsOutput) + { + seen_input_ports.insert(name); + if (this->configuration.input_added) + this->configuration.input_added(to_port_info(client, port)); + } + else if (flags & JackPortIsInput) + { + seen_output_ports.insert(name); + if (this->configuration.output_added) + this->configuration.output_added(to_port_info(client, port)); + } + } + else + { + if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) + { + if (this->configuration.input_removed) + this->configuration.input_removed(to_port_info(client, port)); + seen_input_ports.erase(it); + } + if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) + { + if (this->configuration.output_removed) + this->configuration.output_removed(to_port_info(client, port)); + seen_output_ports.erase(it); + } + } + } + + void set_callbacks() + { + initial_callback(); + + if (!configuration.has_callbacks()) + return; + + jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) { + auto& self = *(observer_jack*)arg; + if (auto port = jack_port_by_id(self.client, p)) + { + self.on_port_callback(port, r != 0); + } + }, this); + + jack_set_port_rename_callback( + this->client, + +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { + const auto& self = *static_cast(arg); + + auto port = jack_port_by_id(self.client, p); + if (!port) + return; + }, this); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + std::vector get_input_ports() const noexcept override + { + return get_ports(this->client, nullptr, port_type, JackPortIsOutput, true); + } + + std::vector get_output_ports() const noexcept override + { + return get_ports(this->client, nullptr, port_type, JackPortIsInput, true); + } + + ~observer_jack() + { + if (client && !configuration.context) + { + // If we own the client, deactivate it + jack_deactivate(this->client); + jack_client_close(this->client); + this->client = nullptr; + } + } + + std::unordered_set seen_input_ports; + std::unordered_set seen_output_ports; +}; +} diff --git a/include/libremidi/backends/jack_ump/shared_handler.hpp b/include/libremidi/backends/jack_ump/shared_handler.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/libremidi/backends/keyboard.hpp b/include/libremidi/backends/keyboard.hpp index 4674341..6d18a31 100644 --- a/include/libremidi/backends/keyboard.hpp +++ b/include/libremidi/backends/keyboard.hpp @@ -2,6 +2,8 @@ #include #include +#include + namespace libremidi { struct kbd_backend @@ -13,8 +15,8 @@ struct kbd_backend using midi_out_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration; static const constexpr auto API = libremidi::API::KEYBOARD; - static const constexpr auto name = "keyboard"; - static const constexpr auto display_name = "Computer keyboard"; + static const constexpr std::string_view name = "keyboard"; + static const constexpr std::string_view display_name = "Computer keyboard"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/keyboard/config.hpp b/include/libremidi/backends/keyboard/config.hpp index 109122e..401728e 100644 --- a/include/libremidi/backends/keyboard/config.hpp +++ b/include/libremidi/backends/keyboard/config.hpp @@ -6,6 +6,128 @@ namespace libremidi { +enum kbd_event +{ + NOTE_0 = 0x0, // C + VEL_0 = NOTE_0 + 128, // Set velocity to 0 + OCT_0 = VEL_0 + 128, // Set octave to 0 + OCTAVE_PLUS = OCT_0 + 128, + OCTAVE_MINUS, + VELOCITY_PLUS, + VELOCITY_MINUS, +}; + +// Default map +// +// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------, +// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- | +// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----| +// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| | +// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| | +// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | | +// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----| +// | -^ | | O-| O+| V-| V+| | | | | | | ----^ | +// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------| +// | ctrl | | alt | |altgr | | ctrl | +// '------' '-----'--------------------------'------' '------' + +// clang-format off + +// actually based on macOS virtual codes as it seems +// impossible to get raw scan codes. +// https://eastmanreference.com/complete-list-of-applescript-key-codes +static inline const std::map& scancode_map_macos(){ + static const std::map ret{ + { 0, NOTE_0}, // C0 + { 13, NOTE_0 + 1}, + { 1, NOTE_0 + 2}, + { 14, NOTE_0 + 3}, + { 2, NOTE_0 + 4}, + { 3, NOTE_0 + 5}, + { 17, NOTE_0 + 6}, + { 5, NOTE_0 + 7}, + { 16, NOTE_0 + 8}, + { 4, NOTE_0 + 9}, + { 32, NOTE_0 + 10}, + { 38, NOTE_0 + 11}, + { 40, NOTE_0 + 12}, // C1 + { 31, NOTE_0 + 13}, + { 37, NOTE_0 + 14}, + { 35, NOTE_0 + 15}, + { 41, NOTE_0 + 16}, + { 39, NOTE_0 + 17}, + { 30, NOTE_0 + 18}, + { 42, NOTE_0 + 19}, + + { 50, VEL_0 + int(0)}, + { 18, VEL_0 + int(1 * 127 / 12)}, + { 19, VEL_0 + int(2 * 127 / 12)}, + { 20, VEL_0 + int(3 * 127 / 12)}, + { 21, VEL_0 + int(4 * 127 / 12)}, + { 23, VEL_0 + int(5 * 127 / 12)}, + { 22, VEL_0 + int(6 * 127 / 12)}, + { 26, VEL_0 + int(7 * 127 / 12)}, + { 28, VEL_0 + int(8 * 127 / 12)}, + { 25, VEL_0 + int(9 * 127 / 12)}, + { 29, VEL_0 + int(10 * 127 / 12)}, + { 27, VEL_0 + int(11 * 127 / 12)}, + { 24, VEL_0 + int(127)}, + + { 6, OCTAVE_MINUS }, + { 7, OCTAVE_PLUS }, + { 8, VELOCITY_MINUS }, + { 9, VELOCITY_PLUS}, + }; + return ret; +} + +// Use: https://kbdlayout.info/KBDUSX/scancodes +// Also valid for windows +static inline const std::map& scancode_map_linux() { + static const std::map ret{ + { 0x1E, NOTE_0}, // C0 + { 0x11, NOTE_0 + 1}, + { 0x1F, NOTE_0 + 2}, + { 0x12, NOTE_0 + 3}, + { 0x20, NOTE_0 + 4}, + { 0x21, NOTE_0 + 5}, + { 0x14, NOTE_0 + 6}, + { 0x22, NOTE_0 + 7}, + { 0x15, NOTE_0 + 8}, + { 0x23, NOTE_0 + 9}, + { 0x16, NOTE_0 + 10}, + { 0x24, NOTE_0 + 11}, + { 0x25, NOTE_0 + 12}, // C1 + { 0x18, NOTE_0 + 13}, + { 0x26, NOTE_0 + 14}, + { 0x19, NOTE_0 + 15}, + { 0x27, NOTE_0 + 16}, + { 0x28, NOTE_0 + 17}, + { 0x1B, NOTE_0 + 18}, + { 0x2B, NOTE_0 + 19}, + + { 0x29, VEL_0 + int(0)}, + { 0x02, VEL_0 + int(1 * 127 / 12)}, + { 0x03, VEL_0 + int(2 * 127 / 12)}, + { 0x04, VEL_0 + int(3 * 127 / 12)}, + { 0x05, VEL_0 + int(4 * 127 / 12)}, + { 0x06, VEL_0 + int(5 * 127 / 12)}, + { 0x07, VEL_0 + int(6 * 127 / 12)}, + { 0x08, VEL_0 + int(7 * 127 / 12)}, + { 0x09, VEL_0 + int(8 * 127 / 12)}, + { 0x0A, VEL_0 + int(9 * 127 / 12)}, + { 0x0B, VEL_0 + int(10 * 127 / 12)}, + { 0x0C, VEL_0 + int(11 * 127 / 12)}, + { 0x0D, VEL_0 + int(127)}, + + { 0x2C, OCTAVE_MINUS }, + { 0x2D, OCTAVE_PLUS }, + { 0x2E, VELOCITY_MINUS }, + { 0x2F, VELOCITY_PLUS}, + }; + return ret; +} +// clang-format on /** * Used to set up keyboard input. * Your app should pass a function that will give you a callback @@ -13,80 +135,20 @@ namespace libremidi */ struct kbd_input_configuration { + // Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk) + // ex.: pass QKeyEvent::nativeScanCode() - 8 to this API using scancode_callback = std::function; + // First argument is on key press, second on key release std::function set_input_scancode_callbacks - = [](scancode_callback, scancode_callback) {}; + = [](const scancode_callback&, const scancode_callback&) { }; - enum kbd_event - { - NOTE_0 = 0x0, // C - VEL_0 = NOTE_0 + 128, // Set velocity to 0 - OCT_0 = VEL_0 + 128, // Set octave to 0 - OCTAVE_PLUS = OCT_0 + 128, - OCTAVE_MINUS, - VELOCITY_PLUS, - VELOCITY_MINUS, - }; - - // Default map - // - // ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------, - // | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- | - // |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----| - // | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| | - // |-----',--',--',--',--',--',--',--',--',--',--',--',--'| | - // | Caps | C | D | E | F | G | A | B | C | D | E | F | G | | - // |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----| - // | -^ | | O-| O+| V-| V+| | | | | | | ----^ | - // |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------| - // | ctrl | | alt | |altgr | | ctrl | - // '------' '-----'--------------------------'------' '------' - - // clang-format off - // Use: https://kbdlayout.info/KBDUSX/scancodes - std::map scancode_map{ - { 0x1E, NOTE_0}, // C0 - { 0x11, NOTE_0 + 1}, - { 0x1F, NOTE_0 + 2}, - { 0x12, NOTE_0 + 3}, - { 0x20, NOTE_0 + 4}, - { 0x21, NOTE_0 + 5}, - { 0x14, NOTE_0 + 6}, - { 0x22, NOTE_0 + 7}, - { 0x15, NOTE_0 + 8}, - { 0x23, NOTE_0 + 9}, - { 0x16, NOTE_0 + 10}, - { 0x24, NOTE_0 + 11}, - { 0x25, NOTE_0 + 12}, // C1 - { 0x18, NOTE_0 + 13}, - { 0x26, NOTE_0 + 14}, - { 0x19, NOTE_0 + 15}, - { 0x27, NOTE_0 + 16}, - { 0x28, NOTE_0 + 17}, - { 0x1B, NOTE_0 + 18}, - { 0x2B, NOTE_0 + 19}, - - { 0x29, VEL_0 + int(0)}, - { 0x02, VEL_0 + int(1 * 127 / 12)}, - { 0x03, VEL_0 + int(2 * 127 / 12)}, - { 0x04, VEL_0 + int(3 * 127 / 12)}, - { 0x05, VEL_0 + int(4 * 127 / 12)}, - { 0x06, VEL_0 + int(5 * 127 / 12)}, - { 0x07, VEL_0 + int(6 * 127 / 12)}, - { 0x08, VEL_0 + int(7 * 127 / 12)}, - { 0x09, VEL_0 + int(8 * 127 / 12)}, - { 0x0A, VEL_0 + int(9 * 127 / 12)}, - { 0x0B, VEL_0 + int(10 * 127 / 12)}, - { 0x0C, VEL_0 + int(11 * 127 / 12)}, - { 0x0D, VEL_0 + int(127)}, - - { 0x2C, OCTAVE_MINUS }, - { 0x2D, OCTAVE_PLUS }, - { 0x2E, VELOCITY_MINUS }, - { 0x2F, VELOCITY_PLUS}, - }; - // clang-format on + std::map scancode_map +#if defined(__APPLE__) + = scancode_map_macos() +#else + = scancode_map_linux() +#endif + ; }; - } diff --git a/include/libremidi/backends/keyboard/midi_in.hpp b/include/libremidi/backends/keyboard/midi_in.hpp index 5b11124..185b2f9 100644 --- a/include/libremidi/backends/keyboard/midi_in.hpp +++ b/include/libremidi/backends/keyboard/midi_in.hpp @@ -22,22 +22,22 @@ public: explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - configuration.set_input_scancode_callbacks( - [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); }); } ~midi_in_kbd() override { } libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; } - stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; } + stdx::error open_port(const input_port&, std::string_view) override + { + configuration.set_input_scancode_callbacks( + [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); }); - stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; } + return stdx::error{}; + } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); @@ -45,7 +45,7 @@ public: void on_keypress(int scancode) { - using kevent = kbd_input_configuration::kbd_event; + using kevent = libremidi::kbd_event; auto it = configuration.scancode_map.find(scancode); if (it == configuration.scancode_map.end()) @@ -88,7 +88,7 @@ public: void on_keyrelease(int scancode) { - using kevent = kbd_input_configuration::kbd_event; + using kevent = libremidi::kbd_event; auto it = configuration.scancode_map.find(scancode); if (it == configuration.scancode_map.end()) diff --git a/include/libremidi/backends/linux/alsa.hpp b/include/libremidi/backends/linux/alsa.hpp index 2820d5e..a98a18e 100644 --- a/include/libremidi/backends/linux/alsa.hpp +++ b/include/libremidi/backends/linux/alsa.hpp @@ -2,7 +2,7 @@ #include -#include +#include // IWYU pragma: export #if defined(SND_LIB_VERSION) #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6) @@ -241,6 +241,7 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, client_id) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name) + LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_card) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, close) @@ -298,6 +299,7 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, client_id) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name) + LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_card) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, close) diff --git a/include/libremidi/backends/linux/pipewire.hpp b/include/libremidi/backends/linux/pipewire.hpp index c14bd39..4553cec 100644 --- a/include/libremidi/backends/linux/pipewire.hpp +++ b/include/libremidi/backends/linux/pipewire.hpp @@ -12,6 +12,8 @@ namespace libremidi class libpipewire { public: + decltype(&::pw_get_library_version) get_library_version{}; + decltype(&::pw_init) init{}; decltype(&::pw_deinit) deinit{}; @@ -67,12 +69,12 @@ public: bool available{true}; private: - dylib_loader library; + dylib_loader m_library; libpipewire() - : library("libpipewire-0.3.so.0") + : m_library("libpipewire-0.3.so.0") { - if (!library) + if (!m_library) { available = false; return; @@ -81,52 +83,59 @@ private: // in terms of regex: // decltype\(&::([a-z_]+)\) [a-z_]+{}; // \1 = library.symbol("\1"); - init = library.symbol("pw_init"); - deinit = library.symbol("pw_deinit"); + get_library_version + = m_library.symbol("pw_get_library_version"); - context_new = library.symbol("pw_context_new"); - context_connect = library.symbol("pw_context_connect"); - context_destroy = library.symbol("pw_context_destroy"); + init = m_library.symbol("pw_init"); + deinit = m_library.symbol("pw_deinit"); - core_disconnect = library.symbol("pw_core_disconnect"); + context_new = m_library.symbol("pw_context_new"); + context_connect = m_library.symbol("pw_context_connect"); + context_destroy = m_library.symbol("pw_context_destroy"); + + core_disconnect = m_library.symbol("pw_core_disconnect"); proxy_add_listener - = library.symbol("pw_proxy_add_listener"); - proxy_destroy = library.symbol("pw_proxy_destroy"); + = m_library.symbol("pw_proxy_add_listener"); + proxy_destroy = m_library.symbol("pw_proxy_destroy"); - main_loop_new = library.symbol("pw_main_loop_new"); - main_loop_destroy = library.symbol("pw_main_loop_destroy"); - main_loop_quit = library.symbol("pw_main_loop_quit"); - main_loop_run = library.symbol("pw_main_loop_run"); + main_loop_new = m_library.symbol("pw_main_loop_new"); + main_loop_destroy + = m_library.symbol("pw_main_loop_destroy"); + main_loop_quit = m_library.symbol("pw_main_loop_quit"); + main_loop_run = m_library.symbol("pw_main_loop_run"); main_loop_get_loop - = library.symbol("pw_main_loop_get_loop"); + = m_library.symbol("pw_main_loop_get_loop"); - properties_new = library.symbol("pw_properties_new"); - properties_free = library.symbol("pw_properties_free"); - properties_get = library.symbol("pw_properties_get"); + properties_new = m_library.symbol("pw_properties_new"); + properties_free = m_library.symbol("pw_properties_free"); + properties_get = m_library.symbol("pw_properties_get"); - filter_new_simple = library.symbol("pw_filter_new_simple"); + filter_new_simple + = m_library.symbol("pw_filter_new_simple"); filter_get_node_id - = library.symbol("pw_filter_get_node_id"); + = m_library.symbol("pw_filter_get_node_id"); filter_get_properties - = library.symbol("pw_filter_get_properties"); - filter_add_port = library.symbol("pw_filter_add_port"); + = m_library.symbol("pw_filter_get_properties"); + filter_add_port = m_library.symbol("pw_filter_add_port"); filter_remove_port - = library.symbol("pw_filter_remove_port"); - filter_update_properties - = library.symbol("pw_filter_update_properties"); + = m_library.symbol("pw_filter_remove_port"); + filter_update_properties = m_library.symbol( + "pw_filter_update_properties"); filter_update_params - = library.symbol("pw_filter_update_params"); - filter_get_time = library.symbol("pw_filter_get_time"); - filter_destroy = library.symbol("pw_filter_destroy"); - filter_connect = library.symbol("pw_filter_connect"); + = m_library.symbol("pw_filter_update_params"); + filter_get_time = m_library.symbol("pw_filter_get_time"); + filter_destroy = m_library.symbol("pw_filter_destroy"); + filter_connect = m_library.symbol("pw_filter_connect"); filter_get_dsp_buffer - = library.symbol("pw_filter_get_dsp_buffer"); + = m_library.symbol("pw_filter_get_dsp_buffer"); filter_dequeue_buffer - = library.symbol("pw_filter_dequeue_buffer"); + = m_library.symbol("pw_filter_dequeue_buffer"); filter_queue_buffer - = library.symbol("pw_filter_queue_buffer"); - filter_flush = library.symbol("pw_filter_flush"); + = m_library.symbol("pw_filter_queue_buffer"); + filter_flush = m_library.symbol("pw_filter_flush"); + + assert(get_library_version); assert(init); assert(deinit); diff --git a/include/libremidi/backends/linux/udev.hpp b/include/libremidi/backends/linux/udev.hpp index 48e26cb..bffa857 100644 --- a/include/libremidi/backends/linux/udev.hpp +++ b/include/libremidi/backends/linux/udev.hpp @@ -1,10 +1,12 @@ #pragma once #include #include +#include #include #include +#include namespace libremidi { @@ -25,12 +27,28 @@ struct libudev LIBREMIDI_SYMBOL_INIT(udev, device_get_action) LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem) + LIBREMIDI_SYMBOL_INIT(udev, device_get_property_value) + LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath) + LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype) LIBREMIDI_SYMBOL_INIT(udev, device_unref) + LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd) LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device) LIBREMIDI_SYMBOL_INIT(udev, monitor_unref) + + LIBREMIDI_SYMBOL_INIT(udev, enumerate_new) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry) + + LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name) + LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next) + LIBREMIDI_SYMBOL_INIT2(udev, new, create) LIBREMIDI_SYMBOL_INIT(udev, unref) } @@ -46,12 +64,28 @@ struct libudev LIBREMIDI_SYMBOL_DEF(udev, device_get_action) LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem) + LIBREMIDI_SYMBOL_DEF(udev, device_new_from_syspath) + LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value) + LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype) LIBREMIDI_SYMBOL_DEF(udev, device_unref) + LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd) LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device) LIBREMIDI_SYMBOL_DEF(udev, monitor_unref) + + LIBREMIDI_SYMBOL_DEF(udev, enumerate_new) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry) + + LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name) + LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next) + LIBREMIDI_SYMBOL_DEF2(udev, new, create) LIBREMIDI_SYMBOL_DEF(udev, unref) }; @@ -89,4 +123,72 @@ struct udev_helper udev_monitor* monitor{}; }; +struct udev_enumerator +{ + explicit udev_enumerator(const udev_helper& h) + : helper{h} + { + auto& udev = helper.udev; + auto& instance = helper.instance; + enumerator = udev.enumerate_new(instance); + assert(enumerator); + } + + ~udev_enumerator() + { + auto& udev = helper.udev; + udev.enumerate_unref(enumerator); + } + + const udev_helper& helper; + udev_enumerate* enumerator{}; + operator udev_enumerate*() const noexcept { return enumerator; } +}; + +struct udev_soundcard_info +{ + container_identifier container; + device_identifier path; + port_information::port_type type{}; +}; + +inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card) +{ + auto& udev = helper.udev; + udev_enumerator e{helper}; + udev.enumerate_add_match_subsystem(e, "sound"); + udev.enumerate_add_match_sysname(e, ("controlC" + std::to_string(card)).c_str()); + if (udev.enumerate_scan_devices(e) != 0) + return {}; + + for (auto entry = udev.enumerate_get_list_entry(e); entry; + entry = udev.list_entry_get_next(entry)) + { + auto path = udev.list_entry_get_name(entry); + if (!path) + continue; + + auto dev = udev.device_new_from_syspath(helper.instance, path); + if (!dev) + continue; + + const auto usb_device + = udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"); + + port_information::port_type type = port_information::port_type::hardware; + if (usb_device) + type = (port_information::port_type)(type | port_information::port_type::usb); + else + type = (port_information::port_type)(type | port_information::port_type::pci); + + auto id_path = udev.device_get_property_value(dev, "ID_PATH"); + if (!id_path) + id_path = ""; + + auto ret = udev_soundcard_info{.container = id_path, .path = path, .type = type}; + udev.device_unref(dev); + return ret; + } + return {}; +} } diff --git a/include/libremidi/backends/net/helpers.hpp b/include/libremidi/backends/net/helpers.hpp index 54d4879..11847fc 100644 --- a/include/libremidi/backends/net/helpers.hpp +++ b/include/libremidi/backends/net/helpers.hpp @@ -42,31 +42,34 @@ struct optionally_owned { public: explicit optionally_owned(T* maybe_existing) - : storage{.ref = maybe_existing} + : m_storage{.ref = maybe_existing} { - if (storage.ref) + if (m_storage.ref) { - ownership = unowned; + m_ownership = unowned; return; } else { - std::destroy_at(&storage.ref); - std::construct_at(reinterpret_cast(storage.object)); - ownership = owned; + std::destroy_at(&m_storage.ref); + std::construct_at(reinterpret_cast(m_storage.object)); + m_ownership = owned; } } ~optionally_owned() { if (is_owned()) - std::destroy_at(reinterpret_cast(storage.object)); + std::destroy_at(reinterpret_cast(m_storage.object)); } - T& get() noexcept { return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); } + T& get() noexcept + { + return *(is_owned() ? reinterpret_cast(&m_storage.object) : m_storage.ref); + } const T& get() const noexcept { - return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); + return *(is_owned() ? reinterpret_cast(&m_storage.object) : m_storage.ref); } optionally_owned(const optionally_owned&) = delete; @@ -74,18 +77,18 @@ public: optionally_owned& operator=(const optionally_owned&) = delete; optionally_owned& operator=(optionally_owned&&) noexcept = delete; - bool is_owned() const noexcept { return ownership == owned; } + bool is_owned() const noexcept { return m_ownership == owned; } private: union { alignas(T) unsigned char object[sizeof(T)]; T* ref; - } storage; + } m_storage; enum { owned, unowned - } ownership{}; + } m_ownership{}; }; } diff --git a/include/libremidi/backends/net/midi_in.hpp b/include/libremidi/backends/net/midi_in.hpp index abf5c67..605d73d 100644 --- a/include/libremidi/backends/net/midi_in.hpp +++ b/include/libremidi/backends/net/midi_in.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace libremidi { template @@ -219,8 +220,6 @@ public: return {}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); @@ -394,8 +393,6 @@ public: return {}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); diff --git a/include/libremidi/backends/network.hpp b/include/libremidi/backends/network.hpp index 42930e5..9ebc122 100644 --- a/include/libremidi/backends/network.hpp +++ b/include/libremidi/backends/network.hpp @@ -5,6 +5,8 @@ #include #include +#include + namespace libremidi::net { struct backend @@ -16,8 +18,8 @@ struct backend using midi_out_configuration = libremidi::net::dgram_output_configuration; using midi_observer_configuration = libremidi::net::net_observer_configuration; static const constexpr auto API = libremidi::API::NETWORK; - static const constexpr auto name = "network"; - static const constexpr auto display_name = "Network"; + static const constexpr std::string_view name = "network"; + static const constexpr std::string_view display_name = "Network"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/network_ump.hpp b/include/libremidi/backends/network_ump.hpp index 7b12562..00c4807 100644 --- a/include/libremidi/backends/network_ump.hpp +++ b/include/libremidi/backends/network_ump.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace libremidi::net_ump { struct backend @@ -15,8 +17,8 @@ struct backend using midi_out_configuration = libremidi::net_ump::dgram_output_configuration; using midi_observer_configuration = libremidi::net_ump::net_observer_configuration; static const constexpr auto API = libremidi::API::NETWORK_UMP; - static const constexpr auto name = "network (UMP)"; - static const constexpr auto display_name = "Network (UMP)"; + static const constexpr std::string_view name = "network (UMP)"; + static const constexpr std::string_view display_name = "Network (UMP)"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/pipewire.hpp b/include/libremidi/backends/pipewire.hpp index f3a9a96..e66760a 100644 --- a/include/libremidi/backends/pipewire.hpp +++ b/include/libremidi/backends/pipewire.hpp @@ -7,6 +7,8 @@ #include #include +#include + namespace libremidi::pipewire { struct backend @@ -18,8 +20,8 @@ struct backend using midi_out_configuration = pipewire_output_configuration; using midi_observer_configuration = pipewire_observer_configuration; static const constexpr auto API = libremidi::API::PIPEWIRE; - static const constexpr auto name = "pipewire"; - static const constexpr auto display_name = "PipeWire"; + static const constexpr std::string_view name = "pipewire"; + static const constexpr std::string_view display_name = "PipeWire"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/pipewire/context.hpp b/include/libremidi/backends/pipewire/context.hpp index b9a8635..5256904 100644 --- a/include/libremidi/backends/pipewire/context.hpp +++ b/include/libremidi/backends/pipewire/context.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -79,11 +80,11 @@ struct pipewire_context struct graph { mutable std::mutex mtx; - libremidi::hash_map physical_audio; - libremidi::hash_map physical_midi; - libremidi::hash_map software_audio; - libremidi::hash_map software_midi; - libremidi::hash_map port_cache; + libremidi::hash_map physical_audio; + libremidi::hash_map physical_midi; + libremidi::hash_map software_audio; + libremidi::hash_map software_midi; + libremidi::hash_map port_cache; void for_each_port(auto func) { @@ -123,7 +124,7 @@ struct pipewire_context } explicit pipewire_context(std::shared_ptr inst) - : global_instance{inst} + : global_instance{std::move(inst)} , owns_main_loop{true} { this->main_loop = pw.main_loop_new(nullptr); @@ -287,7 +288,7 @@ struct pipewire_context spa_hook_remove(&core_listener); } - [[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port) + [[nodiscard]] pw_proxy* link_ports(uint64_t out_port, uint64_t in_port) { auto props = pw.properties_new( PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT, @@ -361,6 +362,8 @@ struct pipewire_context return &this->current_graph.physical_audio[nid]; else if (p.format.find("midi") != p.format.npos) return &this->current_graph.physical_midi[nid]; + else if (p.format.find("UMP") != p.format.npos) + return &this->current_graph.physical_midi[nid]; } else { @@ -368,6 +371,8 @@ struct pipewire_context return &this->current_graph.software_audio[nid]; else if (p.format.find("midi") != p.format.npos) return &this->current_graph.software_midi[nid]; + else if (p.format.find("UMP") != p.format.npos) + return &this->current_graph.software_midi[nid]; } return nullptr; }; @@ -453,12 +458,12 @@ struct pipewire_filter }* port{}; explicit pipewire_filter(std::shared_ptr loop) - : loop{loop} + : loop{std::move(loop)} { } explicit pipewire_filter(std::shared_ptr loop, pw_filter* filter) - : loop{loop} + : loop{std::move(loop)} , filter{filter} { } @@ -498,7 +503,8 @@ struct pipewire_filter pw.filter_destroy(this->filter); } - stdx::error create_local_port(std::string_view port_name, spa_direction direction) + stdx::error + create_local_port(std::string_view port_name, spa_direction direction, const char* format) { // clang-format off this->port = (struct port*)pw.filter_add_port( @@ -507,7 +513,7 @@ struct pipewire_filter PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw.properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, format, PW_KEY_PORT_NAME, port_name.data(), nullptr), nullptr, 0); @@ -517,7 +523,7 @@ struct pipewire_filter return stdx::error{}; } - void set_port_buffer(int bytes) + void set_port_buffer(int64_t bytes) { uint8_t buffer[1024]; struct spa_pod_builder builder; diff --git a/include/libremidi/backends/pipewire/helpers.hpp b/include/libremidi/backends/pipewire/helpers.hpp index 8c2970c..3de21c6 100644 --- a/include/libremidi/backends/pipewire/helpers.hpp +++ b/include/libremidi/backends/pipewire/helpers.hpp @@ -9,8 +9,6 @@ #include #include -#include -#include #include namespace libremidi @@ -66,14 +64,14 @@ struct pipewire_helpers { this->filter = std::make_unique(this->global_context, configuration.filter); - pipewire_callback cbs{ + libremidi::pipewire_callback cbs{ .token = this_instance, .callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void { - if (auto pt = p.lock()) - self.process(nf); + if (auto pt = p.lock()) + self.process(nf); - self.thread_lock.check_client_released(); - }}; + self.thread_lock.check_client_released(); + }}; configuration.set_process_func(cbs); } else @@ -197,7 +195,8 @@ struct pipewire_helpers } template - stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction) + stdx::error create_local_port( + Self& self, std::string_view portName, spa_direction direction, const char* format) { assert(this->global_context); assert(this->filter); @@ -207,7 +206,7 @@ struct pipewire_helpers if (!this->filter->port) { - auto ret = this->filter->create_local_port(portName.data(), direction); + auto ret = this->filter->create_local_port(portName.data(), direction, format); if (ret != stdx::error{}) { self.libremidi_handle_error(self.configuration, "error creating port"); @@ -218,11 +217,11 @@ struct pipewire_helpers return stdx::error{}; } - void add_callbacks(const observer_configuration& conf) + void add_callbacks(std::string format, const observer_configuration& conf) { assert(global_context); - global_context->on_port_added = [&conf](const pipewire_context::port_info& port) { - if (port.format.find("midi") == std::string::npos) + global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) { + if (port.format.find(format) == std::string::npos) return; bool unfiltered = conf.track_any; @@ -243,8 +242,8 @@ struct pipewire_helpers } }; - global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) { - if (port.format.find("midi") == std::string::npos) + global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) { + if (port.format.find(format) == std::string::npos) return; bool unfiltered = conf.track_any; @@ -437,7 +436,9 @@ struct pipewire_helpers // Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer) // is an "output" port from the point of view of pipewire as data will come out of it template - static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept + static auto get_ports( + std::string_view format, const observer_configuration& conf, + const pipewire_context& ctx) noexcept -> std::vector< std::conditional_t> { @@ -452,7 +453,8 @@ struct pipewire_helpers for (auto& port : (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { - ret.push_back(to_port_info(port)); + if (port.format.find(format) != std::string::npos) + ret.push_back(to_port_info(port)); } } @@ -462,7 +464,8 @@ struct pipewire_helpers for (auto& port : (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { - ret.push_back(to_port_info(port)); + if (port.format.find(format) != std::string::npos) + ret.push_back(to_port_info(port)); } } } diff --git a/include/libremidi/backends/pipewire/midi_in.hpp b/include/libremidi/backends/pipewire/midi_in.hpp index a584b2d..163ef0a 100644 --- a/include/libremidi/backends/pipewire/midi_in.hpp +++ b/include/libremidi/backends/pipewire/midi_in.hpp @@ -47,7 +47,8 @@ public: stdx::error open_port(const input_port& in_port, std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi"); + err != stdx::error{}) return err; if (auto err = link_ports(*this, in_port); err != stdx::error{}) @@ -59,7 +60,8 @@ public: stdx::error open_virtual_port(std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi"); + err != stdx::error{}) return err; start_thread(); diff --git a/include/libremidi/backends/pipewire/midi_out.hpp b/include/libremidi/backends/pipewire/midi_out.hpp index e8ae5c5..fcb5d90 100644 --- a/include/libremidi/backends/pipewire/midi_out.hpp +++ b/include/libremidi/backends/pipewire/midi_out.hpp @@ -5,8 +5,6 @@ #include -#include - namespace libremidi { class midi_out_pipewire @@ -50,7 +48,8 @@ public: stdx::error open_port(const output_port& out_port, std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi"); + err != stdx::error{}) return err; this->filter->set_port_buffer(configuration.output_buffer_size); @@ -64,7 +63,8 @@ public: stdx::error open_virtual_port(std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi"); + err != stdx::error{}) return err; this->filter->set_port_buffer(configuration.output_buffer_size); @@ -121,8 +121,9 @@ public: continue; } - spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi); - int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size()); + spa_pod_builder_control(&build, static_cast(m.timestamp), SPA_CONTROL_Midi); + int res + = spa_pod_builder_bytes(&build, m.bytes.data(), static_cast(m.bytes.size())); // Try again next buffer if (res == -ENOSPC) diff --git a/include/libremidi/backends/pipewire/observer.hpp b/include/libremidi/backends/pipewire/observer.hpp index 66eba74..57d8127 100644 --- a/include/libremidi/backends/pipewire/observer.hpp +++ b/include/libremidi/backends/pipewire/observer.hpp @@ -24,19 +24,9 @@ public: { create_context(*this); - // FIXME notify_in_constructor // FIXME port rename callback -#if 0 - // Initialize PipeWire client - if (configuration.context) { - this->client = configuration.context; - set_callbacks(); - } - else -#endif - { - this->add_callbacks(configuration); + this->add_callbacks("midi", configuration); this->start_thread(); } @@ -56,27 +46,18 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports(this->configuration, *this->global_context); + return get_ports("midi", this->configuration, *this->global_context); } std::vector get_output_ports() const noexcept override { - return get_ports(this->configuration, *this->global_context); + return get_ports("midi", this->configuration, *this->global_context); } ~observer_pipewire() { stop_thread(); destroy_context(); -#if 0 - if (client && !configuration.context) - { - // If we own the client, deactivate it - pipewire_deactivate(this->client); - pipewire_client_close(this->client); - this->client = nullptr; - } -#endif } std::unordered_set seen_input_ports; diff --git a/include/libremidi/backends/pipewire_ump.hpp b/include/libremidi/backends/pipewire_ump.hpp new file mode 100644 index 0000000..3b1cb0f --- /dev/null +++ b/include/libremidi/backends/pipewire_ump.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +namespace libremidi::pipewire_ump +{ +struct backend +{ + using midi_in = libremidi::pipewire_ump::midi_in_pipewire; + using midi_out = libremidi::pipewire_ump::midi_out_pipewire; + using midi_observer = libremidi::pipewire_ump::observer_pipewire; + using midi_in_configuration = libremidi::pipewire_ump::input_configuration; + using midi_out_configuration = libremidi::pipewire_ump::output_configuration; + using midi_observer_configuration = libremidi::pipewire_ump::observer_configuration; + static const constexpr auto API = libremidi::API::PIPEWIRE_UMP; + static const constexpr std::string_view name = "pipewire_ump"; + static const constexpr std::string_view display_name = "PipeWire (UMP)"; + + static inline bool available() noexcept + { + static const libpipewire& pw = libpipewire::instance(); + if (!pw.available) + return false; + const std::string_view version = pw.get_library_version(); + if (version.size() >= 3) + { + if (version[0] > '1' || version[2] >= '4') + return true; + } + return false; + } +}; +} diff --git a/include/libremidi/backends/pipewire_ump/config.hpp b/include/libremidi/backends/pipewire_ump/config.hpp new file mode 100644 index 0000000..a9dcb58 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/config.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +namespace libremidi::pipewire_ump +{ +struct input_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; + pw_filter* filter{}; + std::function set_process_func{}; + std::function clear_process_func{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; + pw_filter* filter{}; + std::function set_process_func{}; + std::function clear_process_func{}; + + int64_t output_buffer_size{65536}; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; +}; + +} diff --git a/include/libremidi/backends/pipewire_ump/midi_in.hpp b/include/libremidi/backends/pipewire_ump/midi_in.hpp new file mode 100644 index 0000000..129f147 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/midi_in.hpp @@ -0,0 +1,135 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi::pipewire_ump +{ +class midi_in_pipewire final + : public midi2::in_api + , public pipewire_helpers + , public error_handler +{ +public: + struct + : ump_input_configuration + , libremidi::pipewire_ump::input_configuration + { + } configuration; + + explicit midi_in_pipewire( + ump_input_configuration&& conf, libremidi::pipewire_ump::input_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (auto ret = create_context(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + if (auto ret = create_filter(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + client_open_ = stdx::error{}; + } + + ~midi_in_pipewire() override + { + stop_thread(); + do_close_port(); + destroy_filter(*this); + destroy_context(); + client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + stdx::error open_port(const input_port& in_port, std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + if (auto err = link_ports(*this, in_port); err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error close_port() override + { + stop_thread(); + return do_close_port(); + } + + stdx::error set_port_name(std::string_view port_name) override + { + return rename_port(port_name); + } + + timestamp absolute_timestamp() const noexcept override { return system_ns(); } + + void process(struct spa_io_position* position) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = true, + .has_samples = true, + }; + + assert(this->filter); + assert(this->filter->port); + const auto b = pw.filter_dequeue_buffer(this->filter->port); + if (!b) + return; + + const auto buf = b->buffer; + const auto d = &buf->datas[0]; + + if (d->data == nullptr) + return; + + const auto pod + = (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); + if (!pod) + return; + if (!spa_pod_is_sequence(pod)) + return; + + struct spa_pod_control* c{}; + SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) + { + if (c->type != SPA_CONTROL_UMP) + continue; + + auto data = (uint8_t*)SPA_POD_BODY(&c->value); + auto size = SPA_POD_BODY_SIZE(&c->value); + + const auto to_ns = [=, clk = position->clock] { + return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom); + }; + + m_processing.on_bytes( + {(uint32_t*)data, (uint32_t*)(data + size)}, + m_processing.timestamp(to_ns, c->offset)); + } + + pw.filter_queue_buffer(this->filter->port, b); + } + + midi2::input_state_machine m_processing{this->configuration}; +}; +} diff --git a/include/libremidi/backends/pipewire_ump/midi_out.hpp b/include/libremidi/backends/pipewire_ump/midi_out.hpp new file mode 100644 index 0000000..f584d77 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/midi_out.hpp @@ -0,0 +1,185 @@ +#pragma once +#include +#include +#include + +#include + +namespace libremidi::pipewire_ump +{ +class midi_out_pipewire + : public midi2::out_api + , public pipewire_helpers + , public error_handler +{ +public: + struct + : libremidi::output_configuration + , libremidi::pipewire_ump::output_configuration + { + } configuration; + + midi_out_pipewire( + libremidi::output_configuration&& conf, + libremidi::pipewire_ump::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (auto ret = create_context(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + if (auto ret = create_filter(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + client_open_ = stdx::error{}; + } + + ~midi_out_pipewire() override + { + stop_thread(); + do_close_port(); + destroy_filter(*this); + destroy_context(); + client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + stdx::error open_port(const output_port& out_port, std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + this->filter->set_port_buffer(configuration.output_buffer_size); + + if (auto err = link_ports(*this, out_port); err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + this->filter->set_port_buffer(configuration.output_buffer_size); + + start_thread(); + return stdx::error{}; + } + + stdx::error close_port() override + { + stop_thread(); + return do_close_port(); + } + + stdx::error set_port_name(std::string_view port_name) override + { + return rename_port(port_name); + } + + int process(spa_io_position* pos) + { + m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed); + const auto b = pw.filter_dequeue_buffer(this->filter->port); + if (!b) + return 1; + + const auto buf = b->buffer; + const auto d = &buf->datas[0]; + + if (d->data == nullptr) + return 1; + + spa_pod_builder build; + spa_zero(build); + spa_pod_builder_init(&build, d->data, d->maxsize); + + spa_pod_frame f; + spa_pod_builder_push_sequence(&build, &f, 0); + + // for all events + while (auto m_ptr = m_queue.peek()) + { + auto& m = *m_ptr; + + spa_pod_builder_control(&build, static_cast(m.timestamp), SPA_CONTROL_UMP); + int res = spa_pod_builder_bytes(&build, m.data, cmidi2_ump_get_message_size_bytes(m.data)); + + // Try again next buffer + if (res == -ENOSPC) + break; + + // Recycle the memory + m_queue.pop(); + } + spa_pod_builder_pop(&build, &f); + + int n_fill_frames = build.state.offset; + if (n_fill_frames > 0) + { + d->chunk->offset = 0; + d->chunk->stride = 1; + d->chunk->size = n_fill_frames; + b->size = n_fill_frames; + + pw.filter_queue_buffer(this->filter->port, b); + return 0; + } + + pw.filter_flush(this->filter->filter, true); + + return 0; + } + + stdx::error send_ump(const uint32_t* message, size_t size) override + { + // FIXME + if (size > 4) + return std::errc::message_size; + + libremidi::ump m; + std::copy_n(message, size, m.data); + m.timestamp = 0; + m_queue.enqueue(std::move(m)); + return stdx::error{}; + } + + int convert_timestamp(int64_t user) const noexcept + { + switch (configuration.timestamps) + { + case timestamp_mode::AudioFrame: + return static_cast(user); + + default: + // TODO + return 0; + } + } + + stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override + { + // FIXME + if (size > 4) + return std::errc::message_size; + + libremidi::ump m; + std::copy_n(message, size, m.data); + m.timestamp = convert_timestamp(ts); + m_queue.enqueue(std::move(m)); // FIXME actually send them scheudled + return stdx::error{}; + } + + moodycamel::ReaderWriterQueue m_queue; + std::atomic_int64_t m_process_clock = 0; +}; +} diff --git a/include/libremidi/backends/pipewire_ump/observer.hpp b/include/libremidi/backends/pipewire_ump/observer.hpp new file mode 100644 index 0000000..7d52db1 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/observer.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include + +#include + +namespace libremidi::pipewire_ump +{ +class observer_pipewire final + : public observer_api + , private pipewire_helpers + , private error_handler +{ +public: + struct + : libremidi::observer_configuration + , libremidi::pipewire_ump::observer_configuration + { + } configuration; + + explicit observer_pipewire( + libremidi::observer_configuration&& conf, + libremidi::pipewire_ump::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + create_context(*this); + + // FIXME port rename callback + { + this->add_callbacks("UMP", configuration); + this->start_thread(); + } + + if (configuration.notify_in_constructor) + { + if (configuration.input_added) + for (const auto& p : get_input_ports()) + configuration.input_added(p); + + if (configuration.output_added) + for (const auto& p : get_output_ports()) + configuration.output_added(p); + } + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + std::vector get_input_ports() const noexcept override + { + return get_ports("UMP", this->configuration, *this->global_context); + } + + std::vector get_output_ports() const noexcept override + { + return get_ports("UMP", this->configuration, *this->global_context); + } + + ~observer_pipewire() + { + stop_thread(); + destroy_context(); + } + + std::unordered_set seen_input_ports; + std::unordered_set seen_output_ports; +}; +} diff --git a/include/libremidi/backends/winmidi.hpp b/include/libremidi/backends/winmidi.hpp index be7788c..9839d4c 100644 --- a/include/libremidi/backends/winmidi.hpp +++ b/include/libremidi/backends/winmidi.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace libremidi::winmidi { struct backend @@ -14,8 +16,8 @@ struct backend using midi_out_configuration = winmidi::output_configuration; using midi_observer_configuration = winmidi::observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES; - static const constexpr auto name = "winmidi"; - static const constexpr auto display_name = "Windows MIDI Services"; + static const constexpr std::string_view name = "winmidi"; + static const constexpr std::string_view display_name = "Windows MIDI Services"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/winmidi/config.hpp b/include/libremidi/backends/winmidi/config.hpp index 276b616..a8d1b98 100644 --- a/include/libremidi/backends/winmidi/config.hpp +++ b/include/libremidi/backends/winmidi/config.hpp @@ -1,19 +1,34 @@ #pragma once #include +#include + +namespace winrt::Microsoft::Windows::Devices::Midi2 +{ +class MidiSession; +class MidiEndpointDeviceInformation; +class MidiEndpointConnection; +} + +// TODO allow to share midi session and endpoints namespace libremidi::winmidi { struct input_configuration { + std::string client_name = "libremidi input"; + winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{}; }; struct output_configuration { + std::string client_name = "libremidi output"; + winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{}; }; struct observer_configuration { + std::string client_name = "libremidi observer"; }; } diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index f8218a5..50d07c4 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -3,58 +3,94 @@ #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN 1 #include +#include -#include #include #include -#include -#include #include #include #include #include -#include +#include +#include +// MinGW support +#if !defined(_MSC_VER) +namespace Microsoft::Windows::Devices::Midi2::Initialization +{ +struct IMidiClientInitializer; +struct MidiClientInitializerUuid; +} -namespace midi2 = winrt::Windows::Devices::Midi2; -namespace foundation = winrt::Windows::Foundation; -namespace collections = winrt::Windows::Foundation::Collections; - +__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::IMidiClientInitializer, + 0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80); +__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::MidiClientInitializerUuid, + 0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3); +#endif +#include // clang-format on +namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2; +namespace foundation = winrt::Windows::Foundation; + namespace libremidi::winmidi { using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Storage::Streams; -using namespace winrt::Windows::Devices::Midi2; +using namespace winrt::Microsoft::Windows::Devices::Midi2; using namespace Windows::Devices::Enumeration; +using namespace ::Microsoft::Windows::Devices::Midi2::Initialization; inline bool ichar_equals(char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); } + inline bool iequals(std::string_view lhs, std::string_view rhs) { return std::ranges::equal(lhs, rhs, ichar_equals); } -inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation -get_port_by_name(const std::string& port) { +inline std::pair< + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointDeviceInformation, + winrt::Microsoft::Windows::Devices::Midi2::MidiGroupTerminalBlock> +get_port(const std::string& device_name, int group_terminal_block) +{ auto eps = MidiEndpointDeviceInformation::FindAll(); for (const auto& ep : eps) { - auto str = to_string(ep.Id()); + auto str = to_string(ep.EndpointDeviceId()); if (str.empty()) continue; - if (iequals(str, port)) - return ep; + if (iequals(str, device_name)) + { + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Number() == group_terminal_block) + { + return std::make_pair(ep, gp); + } + } + } } - return {nullptr}; + return {nullptr, nullptr}; } +struct winmidi_shared_data +{ + std::shared_ptr initializer; + bool m_ready{false}; + winmidi_shared_data() + : initializer{libremidi::instance()} + , m_ready{ + initializer && initializer->InitializeSdkRuntime() + && initializer->EnsureServiceAvailable()} + { + } +}; } diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 041060e..f37c861 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -5,14 +5,12 @@ #include #include -#include - namespace libremidi::winmidi { - class midi_in_impl final : public midi2::in_api , public error_handler + , public winmidi_shared_data { public: struct @@ -24,7 +22,9 @@ public: explicit midi_in_impl( libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , m_session{MidiSession::CreateSession(L"libremidi session")} + , m_session{ + configuration.context ? *configuration.context + : MidiSession::Create(to_hstring(configuration.client_name))} { this->client_open_ = stdx::error{}; } @@ -42,15 +42,23 @@ public: stdx::error open_port(const input_port& port, std::string_view) override { - auto ep = get_port_by_name(port.port_name); - if (!ep) + auto device_id = std::get_if(&port.device); + if (!device_id) + return std::errc::invalid_argument; + + auto [ep, gp] = get_port(*device_id, port.port); + if (!ep || !gp) return std::errc::address_not_available; - m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_group_filter = port.port - 1; + + // TODO use a MidiGroupEndpointListener for the filtering + m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); m_revoke_token = m_endpoint.MessageReceived( - [&](const foundation::IInspectable& sender, - const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { + [this]( + const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&, + const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { process_message(args); }); @@ -59,7 +67,7 @@ public: return stdx::error{}; } - void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) + void process_message(const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) { static constexpr timestamp_backend_info timestamp_info{ .has_absolute_timestamps = true, @@ -68,6 +76,14 @@ public: }; const auto& ump = msg.GetMessagePacket(); + auto pk = msg.PeekFirstWord(); + if (m_group_filter >= 0) + { + int group = cmidi2_ump_get_group(&pk); + if (group != m_group_filter) + return; + } + const auto& b = ump.GetAllWords(); uint32_t ump_space[64]; @@ -91,7 +107,8 @@ public: private: MidiSession m_session; winrt::event_token m_revoke_token{}; - winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; midi2::input_state_machine m_processing{this->configuration}; + int m_group_filter = -1; }; } diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 3a95546..00817ea 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -11,6 +11,7 @@ namespace libremidi::winmidi class midi_out_impl final : public midi2::out_api , public error_handler + , public winmidi_shared_data { public: struct @@ -21,7 +22,9 @@ public: midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , m_session{MidiSession::CreateSession(L"libremidi session")} + , m_session{ + configuration.context ? *configuration.context + : MidiSession::Create(to_hstring(configuration.client_name))} { this->client_open_ = stdx::error{}; } @@ -35,11 +38,15 @@ public: stdx::error open_port(const output_port& port, std::string_view) override { - auto ep = get_port_by_name(port.port_name); - if (!ep) + auto device_id = std::get_if(&port.device); + if (!device_id) + return std::errc::invalid_argument; + + auto [ep, gp] = get_port(*device_id, port.port); + if (!ep || !gp) return std::errc::address_not_available; - m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); m_endpoint.Open(); return stdx::error{}; @@ -83,7 +90,7 @@ public: private: MidiSession m_session; - winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; }; } diff --git a/include/libremidi/backends/winmidi/observer.hpp b/include/libremidi/backends/winmidi/observer.hpp index 3642324..304179e 100644 --- a/include/libremidi/backends/winmidi/observer.hpp +++ b/include/libremidi/backends/winmidi/observer.hpp @@ -2,7 +2,7 @@ #include #include #include -; + namespace libremidi::winmidi { struct port_info @@ -11,7 +11,9 @@ struct port_info hstring name; }; -class observer_impl final : public observer_api +class observer_impl final + : public observer_api + , public winmidi_shared_data { public: struct @@ -20,11 +22,17 @@ public: { } configuration; + MidiEndpointDeviceWatcher watcher = nullptr; + MidiEndpointDeviceWatcher::Added_revoker m_addHandler; + MidiEndpointDeviceWatcher::Updated_revoker m_updHandler; + MidiEndpointDeviceWatcher::Removed_revoker m_delHandler; + std::map> m_known_input_devices; + std::map> m_known_output_devices; + std::mutex m_devices_mtx; explicit observer_impl( libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , session{MidiSession::CreateSession(L"libremidi session")} { if (!configuration.has_callbacks()) return; @@ -40,26 +48,35 @@ public: configuration.output_added(p); } - /* - evTokenOnInputAdded_ - = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); }); - evTokenOnInputRemoved_ - = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); }); - evTokenOnOutputAdded_ - = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); }); - evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved( - [this](const port_info& p) { on_output_removed(p); }); -*/ + if ((watcher = MidiEndpointDeviceWatcher::Create())) + { + namespace enumeration = winrt::Windows::Devices::Enumeration; + auto addHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationAddedEventArgs>( + this, &observer_impl::on_device_added); + auto updHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationUpdatedEventArgs>( + this, &observer_impl::on_device_updated); + auto delHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationRemovedEventArgs>( + this, &observer_impl::on_device_removed); + + m_addHandler = watcher.Added(winrt::auto_revoke, addHandler); + m_updHandler = watcher.Updated(winrt::auto_revoke, updHandler); + m_delHandler = watcher.Removed(winrt::auto_revoke, delHandler); + + watcher.Start(); + } } ~observer_impl() { if (!configuration.has_callbacks()) return; - // internalInPortObserver_.PortAdded(evTokenOnInputAdded_); - // internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_); - // internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_); - // internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_); + + m_addHandler.revoke(); + m_updHandler.revoke(); + m_delHandler.revoke(); } libremidi::API get_current_api() const noexcept override @@ -67,33 +84,56 @@ public: return libremidi::API::WINDOWS_MIDI_SERVICES; } - template - auto to_port_info(const DeviceInformation& p) const noexcept - -> std::conditional_t + static port_information::port_type code_to_type(std::string_view str) noexcept { + using enum port_information::port_type; + + if (str.starts_with("KS")) + return hardware; + if (str == "BLE") + return port_information::port_type(hardware | bluetooth); + if (str == "VPB" || str == "APP") + return software; + if (str == "LOOP") + return port_information::port_type(software | loopback); + if (str.starts_with("NET")) + return network; + return unknown; + } + + template + auto to_port_info(const MidiEndpointDeviceInformation& p, const MidiGroupTerminalBlock& gp) + const noexcept -> std::conditional_t + { + const auto& tinfo = p.GetTransportSuppliedInfo(); + return { {.client = 0, - .port = 0, - .manufacturer = "", - .device_name = "", - .port_name = to_string(p.Id()), - .display_name = to_string(p.Name())}}; + .container = std::bit_cast(p.ContainerId()), + .device = to_string(p.EndpointDeviceId()), + .port = gp.Number(), + .manufacturer = to_string(tinfo.ManufacturerName), + .device_name = to_string(p.Name()), + .port_name = to_string(gp.Name()), + .display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()), + .type = code_to_type(to_string(p.GetTransportSuppliedInfo().TransportCode))}}; } std::vector get_input_ports() const noexcept override { std::vector ret; - auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); - auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& ep : endpointDevices) + for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { if(ep.Name().starts_with(L"Diagnostics")) { continue; } - // FIXME if(has input...) - ret.emplace_back(to_port_info(ep)); + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockOutput) + ret.emplace_back(to_port_info(ep, gp)); + } } return ret; @@ -103,46 +143,132 @@ public: { std::vector ret; - auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); - auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& ep : endpointDevices) + for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { if(ep.Name().starts_with(L"Diagnostics")) { continue; } - // FIXME if(has output...) - ret.emplace_back(to_port_info(ep)); + + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockInput) + ret.emplace_back(to_port_info(ep, gp)); + } } return ret; } - void on_input_added(const DeviceInformation& name) + // Note: these callbacks are called from some random thread! + void on_device_added( + const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationAddedEventArgs& result) { - if (configuration.input_added) - configuration.input_added(to_port_info(name)); + add_device(result.AddedDevice()); } - void on_input_removed(const DeviceInformation& name) + void on_device_updated( + const MidiEndpointDeviceWatcher& e, + const MidiEndpointDeviceInformationUpdatedEventArgs& result) { + // OPTIMIZEME + remove_device(result.EndpointDeviceId()); + + add_device( + MidiEndpointDeviceInformation::CreateFromEndpointDeviceId(result.EndpointDeviceId())); + } + + void on_device_removed( + const MidiEndpointDeviceWatcher&, + const MidiEndpointDeviceInformationRemovedEventArgs& result) + { + remove_device(result.EndpointDeviceId()); + } + + void add_device(const MidiEndpointDeviceInformation& ep) + { + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + MidiGroupTerminalBlockDirection direction = gp.Direction(); + switch (direction) + { + case MidiGroupTerminalBlockDirection::Bidirectional: { + if (configuration.input_added) + { + auto ip = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); + } + configuration.input_added(std::move(ip)); + } + if (configuration.output_added) + { + auto op = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_output_devices[ep.EndpointDeviceId()].push_back(op); + } + configuration.output_added(std::move(op)); + } + break; + } + case MidiGroupTerminalBlockDirection::BlockInput: + if (configuration.input_added) + { + auto ip = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); + } + configuration.input_added(std::move(ip)); + } + break; + case MidiGroupTerminalBlockDirection::BlockOutput: + if (configuration.output_added) + { + auto op = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_output_devices[ep.EndpointDeviceId()].push_back(op); + } + configuration.output_added(std::move(op)); + } + break; + } + } + } + void remove_device(hstring eid) + { + std::vector to_remove_in; + std::vector to_remove_out; + + { + std::lock_guard _{m_devices_mtx}; + if (auto it = m_known_input_devices.find(eid); it != m_known_input_devices.end()) + { + for (auto& ip : it->second) + { + to_remove_in.push_back(ip); + } + m_known_input_devices.erase(it); + } + if (auto it = m_known_output_devices.find(eid); it != m_known_output_devices.end()) + { + for (auto& op : it->second) + { + to_remove_out.push_back(op); + } + m_known_output_devices.erase(it); + } + } + if (configuration.input_removed) - configuration.input_removed(to_port_info(name)); - } - - void on_output_added(const DeviceInformation& name) - { - if (configuration.output_added) - configuration.output_added(to_port_info(name)); - } - - void on_output_removed(const DeviceInformation& name) - { + for (auto& port : to_remove_in) + configuration.input_removed(port); if (configuration.output_removed) - configuration.output_removed(to_port_info(name)); + for (auto& port : to_remove_out) + configuration.output_removed(port); } - -private: - MidiSession session; }; } diff --git a/include/libremidi/backends/winmm.hpp b/include/libremidi/backends/winmm.hpp index a45692f..6a64d13 100644 --- a/include/libremidi/backends/winmm.hpp +++ b/include/libremidi/backends/winmm.hpp @@ -3,6 +3,8 @@ #include #include +#include + // Default for Windows is to add an identifier to the port names; this // flag can be defined (e.g. in your project file) to disable this behaviour. // #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES @@ -28,8 +30,8 @@ struct winmm_backend using midi_out_configuration = winmm_output_configuration; using midi_observer_configuration = winmm_observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_MM; - static const constexpr auto name = "winmm"; - static const constexpr auto display_name = "Windows Multimedia"; + static const constexpr std::string_view name = "winmm"; + static const constexpr std::string_view display_name = "Windows Multimedia"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/winmm/helpers.hpp b/include/libremidi/backends/winmm/helpers.hpp index ee75550..ef5182a 100644 --- a/include/libremidi/backends/winmm/helpers.hpp +++ b/include/libremidi/backends/winmm/helpers.hpp @@ -1,7 +1,8 @@ #pragma once #include #include -#include + +#include namespace libremidi { @@ -70,5 +71,4 @@ inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumbe deviceName += " "; deviceName += std::to_string(x); } - } diff --git a/include/libremidi/backends/winmm/observer.hpp b/include/libremidi/backends/winmm/observer.hpp index c243bd3..075f4ee 100644 --- a/include/libremidi/backends/winmm/observer.hpp +++ b/include/libremidi/backends/winmm/observer.hpp @@ -3,9 +3,6 @@ #include #include -#include -#include -#include #include namespace libremidi diff --git a/include/libremidi/backends/winuwp.hpp b/include/libremidi/backends/winuwp.hpp index 48e91b3..18a3ecf 100644 --- a/include/libremidi/backends/winuwp.hpp +++ b/include/libremidi/backends/winuwp.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace libremidi { struct winuwp_backend @@ -14,8 +16,8 @@ struct winuwp_backend using midi_out_configuration = winuwp_output_configuration; using midi_observer_configuration = winuwp_observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_UWP; - static const constexpr auto name = "winuwp"; - static const constexpr auto display_name = "Windows UWP"; + static const constexpr std::string_view name = "winuwp"; + static const constexpr std::string_view display_name = "Windows UWP"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/winuwp/observer.hpp b/include/libremidi/backends/winuwp/observer.hpp index 86eeb59..7b5d243 100644 --- a/include/libremidi/backends/winuwp/observer.hpp +++ b/include/libremidi/backends/winuwp/observer.hpp @@ -197,13 +197,13 @@ public: configuration.output_added(p); } - evTokenOnInputAdded_ - = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); }); - evTokenOnInputRemoved_ - = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); }); - evTokenOnOutputAdded_ - = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); }); - evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved( + evTokenOnInputAdded_ = get_internal_in_port_observer().PortAdded( + [this](const port_info& p) { on_input_added(p); }); + evTokenOnInputRemoved_ = get_internal_in_port_observer().PortRemoved( + [this](const port_info& p) { on_input_removed(p); }); + evTokenOnOutputAdded_ = get_internal_out_port_observer().PortAdded( + [this](const port_info& p) { on_output_added(p); }); + evTokenOnOutputRemoved_ = get_internal_out_port_observer().PortRemoved( [this](const port_info& p) { on_output_removed(p); }); } @@ -211,10 +211,10 @@ public: { if (!configuration.has_callbacks()) return; - internalInPortObserver_.PortAdded(evTokenOnInputAdded_); - internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_); - internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_); - internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_); + get_internal_in_port_observer().PortAdded(evTokenOnInputAdded_); + get_internal_in_port_observer().PortRemoved(evTokenOnInputRemoved_); + get_internal_out_port_observer().PortAdded(evTokenOnOutputAdded_); + get_internal_out_port_observer().PortRemoved(evTokenOnOutputRemoved_); } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } @@ -235,7 +235,7 @@ public: std::vector get_input_ports() const noexcept override { std::vector ret; - for (auto& port : internalInPortObserver_.get_ports()) + for (auto& port : get_internal_in_port_observer().get_ports()) ret.push_back(to_port_info(port)); return ret; } @@ -243,18 +243,20 @@ public: std::vector get_output_ports() const noexcept override { std::vector ret; - for (auto& port : internalOutPortObserver_.get_ports()) + for (auto& port : get_internal_out_port_observer().get_ports()) ret.push_back(to_port_info(port)); return ret; } static observer_winuwp_internal& get_internal_in_port_observer() { + static observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; return internalInPortObserver_; } static observer_winuwp_internal& get_internal_out_port_observer() { + static observer_winuwp_internal internalOutPortObserver_{MidiOutPort::GetDeviceSelector()}; return internalOutPortObserver_; } @@ -283,10 +285,6 @@ public: } private: - static inline observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; - static inline observer_winuwp_internal internalOutPortObserver_{ - MidiOutPort::GetDeviceSelector()}; - int evTokenOnInputAdded_{-1}; int evTokenOnInputRemoved_{-1}; int evTokenOnOutputAdded_{-1}; diff --git a/include/libremidi/client.hpp b/include/libremidi/client.hpp index 068c496..5faa69f 100644 --- a/include/libremidi/client.hpp +++ b/include/libremidi/client.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace libremidi::midi1 { @@ -66,8 +67,8 @@ public: } explicit client(const client_configuration& conf, shared_configurations ctx) - : configuration{conf} - , context{ctx} + : m_configuration{conf} + , m_context{std::move(ctx)} , m_observer{ observer_configuration{ .on_error = conf.on_error, @@ -81,10 +82,10 @@ public: .track_hardware = conf.track_hardware, .track_virtual = conf.track_virtual, .notify_in_constructor = false}, - context.observer} + m_context.observer} { - if (context.context) - context.context->start_processing(); + if (m_context.context) + m_context.context->start_processing(); } ~client() @@ -92,8 +93,8 @@ public: m_inputs.clear(); m_outputs.clear(); - if (context.context) - context.context->stop_processing(); + if (m_context.context) + m_context.context->stop_processing(); } std::vector get_input_ports() const noexcept @@ -116,19 +117,19 @@ public: input_configuration{ .on_message = [this, - port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); }, + port](libremidi::message&& m) { m_configuration.on_message(port, std::move(m)); }, .on_raw_data = {}, .get_timestamp = {}, - .on_error = configuration.on_error, - .on_warning = configuration.on_warning, + .on_error = m_configuration.on_error, + .on_warning = m_configuration.on_warning, - .ignore_sysex = configuration.ignore_sysex, - .ignore_timing = configuration.ignore_timing, - .ignore_sensing = configuration.ignore_sensing, + .ignore_sysex = m_configuration.ignore_sysex, + .ignore_timing = m_configuration.ignore_timing, + .ignore_sensing = m_configuration.ignore_sensing, - .timestamps = configuration.timestamps}, - context.in); + .timestamps = m_configuration.timestamps}, + m_context.in); res.first->second.open_port(port, name); } @@ -141,11 +142,11 @@ public: auto res = m_outputs.try_emplace( port, output_configuration{ - .on_error = configuration.on_error, - .on_warning = configuration.on_warning, + .on_error = m_configuration.on_error, + .on_warning = m_configuration.on_warning, - .timestamps = configuration.timestamps}, - context.out); + .timestamps = m_configuration.timestamps}, + m_context.out); res.first->second.open_port(port, name); } @@ -190,8 +191,8 @@ public: } private: - client_configuration configuration; - shared_configurations context; + client_configuration m_configuration; + shared_configurations m_context; std::map m_inputs; std::map m_outputs; diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 534e2b6..580765d 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -311,7 +311,7 @@ enum cmidi2_ump_chord_name_tonic_note CMIDI2_UMP_CHORD_NAME_G = 7, }; -enum cmidi2_ump_chor_name_chord_type +enum cmidi2_ump_chord_name_chord_type { CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0, CMIDI2_UMP_CHORD_TYPE_MAJOR = 1, @@ -1494,7 +1494,7 @@ static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) +static inline uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index e845d4b..64c8ea7 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -1,6 +1,6 @@ #pragma once -#if defined(MSC_VER) +#if defined(_MSC_VER) #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN #endif @@ -62,3 +62,9 @@ using midi_bytes = std::vector; #else #define LIBREMIDI_INLINE #endif + +#if __cpp_contracts >= 202502L + #define LIBREMIDI_PRECONDITION(...) pre(__VA_ARGS__) +#else + #define LIBREMIDI_PRECONDITION(...) +#endif diff --git a/include/libremidi/configurations.hpp b/include/libremidi/configurations.hpp index b872f41..6f04b7b 100644 --- a/include/libremidi/configurations.hpp +++ b/include/libremidi/configurations.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -8,17 +9,60 @@ #include #include #include +#include #include #include #include +#include #include #include #include +#include +#include namespace libremidi { - +struct unspecified_configuration +{ +}; struct dummy_configuration { }; + +using input_api_configuration = std::variant< + unspecified_configuration, dummy_configuration, alsa_raw_input_configuration, + alsa_raw_ump::input_configuration, alsa_seq::input_configuration, + alsa_seq_ump::input_configuration, coremidi_input_configuration, + coremidi_ump::input_configuration, emscripten_input_configuration, jack_input_configuration, + kbd_input_configuration, libremidi::net::dgram_input_configuration, + libremidi::net_ump::dgram_input_configuration, pipewire_input_configuration, + winmidi::input_configuration, winmm_input_configuration, winuwp_input_configuration, + jack_ump::input_configuration, pipewire_ump::input_configuration, libremidi::API>; + +using output_api_configuration = std::variant< + unspecified_configuration, dummy_configuration, alsa_raw_output_configuration, + alsa_raw_ump::output_configuration, alsa_seq::output_configuration, + alsa_seq_ump::output_configuration, coremidi_output_configuration, + coremidi_ump::output_configuration, emscripten_output_configuration, jack_output_configuration, + libremidi::net::dgram_output_configuration, libremidi::net_ump::dgram_output_configuration, + pipewire_output_configuration, winmidi::output_configuration, winmm_output_configuration, + winuwp_output_configuration, jack_ump::output_configuration, + pipewire_ump::output_configuration, libremidi::API>; + +using observer_api_configuration = std::variant< + unspecified_configuration, dummy_configuration, alsa_raw_observer_configuration, + alsa_raw_ump::observer_configuration, alsa_seq::observer_configuration, + alsa_seq_ump::observer_configuration, coremidi_observer_configuration, + coremidi_ump::observer_configuration, emscripten_observer_configuration, + jack_observer_configuration, libremidi::net::dgram_observer_configuration, + libremidi::net_ump::dgram_observer_configuration, pipewire_observer_configuration, + winmidi::observer_configuration, winmm_observer_configuration, winuwp_observer_configuration, + jack_ump::observer_configuration, pipewire_ump::observer_configuration, libremidi::API>; + +LIBREMIDI_EXPORT +libremidi::API midi_api(const input_api_configuration& conf); +LIBREMIDI_EXPORT +libremidi::API midi_api(const output_api_configuration& conf); +LIBREMIDI_EXPORT +libremidi::API midi_api(const observer_api_configuration& conf); } diff --git a/include/libremidi/defaults.hpp b/include/libremidi/defaults.hpp index 6d380dc..edbc888 100644 --- a/include/libremidi/defaults.hpp +++ b/include/libremidi/defaults.hpp @@ -1,11 +1,11 @@ #pragma once #include #include +#include #include #include #include -#include #include namespace libremidi @@ -16,22 +16,22 @@ class observer; //! Get the default midi in configuration object for a given API LIBREMIDI_EXPORT -std::any midi_in_configuration_for(libremidi::API); +input_api_configuration midi_in_configuration_for(libremidi::API); //! Get the default midi out configuration object for a given API LIBREMIDI_EXPORT -std::any midi_out_configuration_for(libremidi::API); +output_api_configuration midi_out_configuration_for(libremidi::API); //! Get the default observer configuration object for a given API LIBREMIDI_EXPORT -std::any observer_configuration_for(libremidi::API); +observer_api_configuration observer_configuration_for(libremidi::API); //! Get a matching midi in configuration object for a given observer instance. LIBREMIDI_EXPORT -std::any midi_in_configuration_for(const libremidi::observer&); +input_api_configuration midi_in_configuration_for(const libremidi::observer&); //! Get a matching midi out configuration object for a given observer instance. LIBREMIDI_EXPORT -std::any midi_out_configuration_for(const libremidi::observer&); +output_api_configuration midi_out_configuration_for(const libremidi::observer&); //! Get the default port for midi input (if any) in a given API. //! Note that this will only return a hardware / physical port. @@ -47,15 +47,15 @@ namespace midi1 { //! Get a default configuration for creating a MIDI 1 input LIBREMIDI_EXPORT -std::any in_default_configuration(); +input_api_configuration in_default_configuration(); //! Get a default configuration for creating a MIDI 1 output LIBREMIDI_EXPORT -std::any out_default_configuration(); +output_api_configuration out_default_configuration(); //! Get a default configuration for creating a MIDI 1 observer LIBREMIDI_EXPORT -std::any observer_default_configuration(); +observer_api_configuration observer_default_configuration(); //! Get a default MIDI 1 input port for the default API of the system inline std::optional in_default_port() noexcept @@ -74,15 +74,15 @@ namespace midi2 { //! Get a default configuration for creating a MIDI 2 input LIBREMIDI_EXPORT -std::any in_default_configuration(); +input_api_configuration in_default_configuration(); //! Get a default configuration for creating a MIDI 2 output LIBREMIDI_EXPORT -std::any out_default_configuration(); +output_api_configuration out_default_configuration(); //! Get a default configuration for creating a MIDI 2 observer LIBREMIDI_EXPORT -std::any observer_default_configuration(); +observer_api_configuration observer_default_configuration(); //! Get a default MIDI 2 input port for the default API of the system inline std::optional in_default_port() noexcept diff --git a/include/libremidi/detail/conversion.hpp b/include/libremidi/detail/conversion.hpp index d627c30..f963feb 100644 --- a/include/libremidi/detail/conversion.hpp +++ b/include/libremidi/detail/conversion.hpp @@ -7,8 +7,193 @@ #include #include #include + +#include + +static inline void cmidi2_reverse(int64_t v, cmidi2_ump* output) +{ + union + { + uint32_t u[2]; + int64_t i; + } e0, e1; + e0.i = v; + e1.u[0] = e0.u[1]; + e1.u[1] = e0.u[0]; + memcpy(output, &e1, 8); +} + +static inline auto u7_to_u16(uint8_t in) -> uint16_t +{ + static constexpr auto ratio = float(std::numeric_limits::max()) / 127.f; + return std::clamp(std::round(in * ratio), 0.f, float(std::numeric_limits::max())); +}; + +static inline auto u7_to_u32(uint8_t in) -> uint32_t +{ + static constexpr auto ratio = double(std::numeric_limits::max()) / 127.; + return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); +}; + +static inline auto u16_to_u32(uint16_t in) -> uint32_t +{ + static constexpr auto ratio = double(std::numeric_limits::max()) + / double(std::numeric_limits::max()); + return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); +}; + +static inline bool +cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2_ump* output) +{ + if (sz < 2) + return false; + if (sz > 3) + return false; + const auto status = libremidi::message_type(bytes[0] & 0xF0); + const int channel = bytes[0] & 0x0F; + const int group = 0; + switch (status) + { + case libremidi::message_type::NOTE_OFF: { + const int64_t msg + = cmidi2_ump_midi2_note_off(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::NOTE_ON: { + const int64_t msg + = cmidi2_ump_midi2_note_on(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::POLY_PRESSURE: { + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, bytes[1], u7_to_u32(bytes[2])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::CONTROL_CHANGE: { + const int64_t msg = cmidi2_ump_midi2_cc(group, channel, bytes[1], u7_to_u32(bytes[2])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::PROGRAM_CHANGE: { + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, bytes[1], 0, 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::AFTERTOUCH: { + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(bytes[1])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::PITCH_BEND: { + const auto pb = bytes[1] + bytes[2] * 0x80; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb)); + cmidi2_reverse(msg, output); + break; + } + default: + return false; + } + + return true; +} + +static inline bool +cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ump* output) +{ + if (cmidi2_ump_get_message_type(input) != CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL) + return false; + + const auto group = cmidi2_ump_get_group(input); + const auto channel = cmidi2_ump_get_channel(input); + const auto status = cmidi2_ump_get_status_code(input); + const auto b1 = cmidi2_ump_get_midi1_byte2(input); + const auto b2 = cmidi2_ump_get_midi1_byte3(input); + + switch (status) + { + // Not in midi 1: + case CMIDI2_STATUS_PER_NOTE_RCC: + case CMIDI2_STATUS_PER_NOTE_ACC: + case CMIDI2_STATUS_RPN: + case CMIDI2_STATUS_NRPN: + case CMIDI2_STATUS_RELATIVE_RPN: + case CMIDI2_STATUS_RELATIVE_NRPN: + case CMIDI2_STATUS_PER_NOTE_PITCH_BEND: + case CMIDI2_STATUS_PER_NOTE_MANAGEMENT: + return false; + + case CMIDI2_STATUS_NOTE_OFF: { + const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, b1, 0, u7_to_u16(b2), 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_NOTE_ON: { + const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, b1, 0, u7_to_u16(b2), 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PAF: { + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, b1, u7_to_u32(b2)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_CC: { + int64_t msg = cmidi2_ump_midi2_cc(group, channel, b1, u7_to_u32(b2)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PROGRAM: { + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, b1, 0, 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_CAF: { + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(b1)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PITCH_BEND: { + const auto pb = b1 + b2 * 0x80; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb)); + cmidi2_reverse(msg, output); + break; + } + } + + return true; +} + namespace libremidi { +static inline libremidi::message midi1_from_ump(libremidi::ump u) +{ + libremidi::message ret; + ret.bytes.resize(4); + if (auto n = cmidi2_convert_single_ump_to_midi1((uint8_t*)ret.bytes.data(), 4, u.data)) + { + ret.timestamp = u.timestamp; + ret.bytes.resize(n); + } + else + { + ret.bytes.clear(); + } + return ret; +} + +static inline libremidi::ump ump_from_midi1(libremidi::message u) +{ + libremidi::ump ret; + if (!u.bytes.empty()) + { + ret.timestamp = u.timestamp; + cmidi2_midi1_channel_voice_to_midi2(u.bytes.data(), u.bytes.size(), ret.data); + } + return ret; +} + struct midi1_to_midi2 { stdx::error @@ -20,11 +205,31 @@ struct midi1_to_midi2 context.ump = ump; context.ump_num_bytes = sizeof(ump); context.ump_proceeded_bytes = 0; + context.skip_delta_time = true; - if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK) - return std::errc::invalid_argument; - - return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); + switch (cmidi2_convert_midi1_to_ump(&context)) + { + case CMIDI2_CONVERSION_RESULT_OK: { + if (auto n = context.ump_proceeded_bytes; n > 0) + return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); + else + return std::errc::operation_not_supported; + } + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + return std::errc::no_buffer_space; + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return std::errc::invalid_argument; + default: + return std::errc::operation_not_supported; + } } cmidi2_midi_conversion_context context = [] { @@ -32,20 +237,45 @@ struct midi1_to_midi2 cmidi2_midi_conversion_context_initialize(&tmp); return tmp; }(); + uint32_t ump[65536 / 4]; }; struct midi2_to_midi1 { - stdx::error - convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi) + stdx::error convert(const uint32_t* message, std::size_t size, int64_t timestamp, auto on_midi) { - auto n - = cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast(message)); - if (n > 0) - return on_midi(midi, n, timestamp); - else - return std::errc::no_buffer_space; + context.midi1 = static_cast(midi); + context.midi1_num_bytes = sizeof(midi); + context.midi1_proceeded_bytes = 0; + context.ump = const_cast(message); + context.ump_num_bytes = size * sizeof(uint32_t); + context.ump_proceeded_bytes = 0; + context.skip_delta_time = true; + + switch (cmidi2_convert_ump_to_midi1(&context)) + { + case CMIDI2_CONVERSION_RESULT_OK: { + if (auto n = context.midi1_proceeded_bytes; n > 0) + return on_midi(midi, n, timestamp); + else + return std::errc::operation_not_supported; + } + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + return std::errc::no_buffer_space; + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + return std::errc::invalid_argument; + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return std::errc::invalid_argument; + default: + return std::errc::operation_not_supported; + } } cmidi2_midi_conversion_context context = [] { @@ -53,6 +283,7 @@ struct midi2_to_midi1 cmidi2_midi_conversion_context_initialize(&tmp); return tmp; }(); + uint8_t midi[65536]; }; diff --git a/include/libremidi/detail/midi_stream_decoder.hpp b/include/libremidi/detail/midi_stream_decoder.hpp index b69fc6b..ec569e2 100644 --- a/include/libremidi/detail/midi_stream_decoder.hpp +++ b/include/libremidi/detail/midi_stream_decoder.hpp @@ -1,11 +1,13 @@ #pragma once +#include +#include #include -#include +#include + #include #include -#include #include namespace libremidi @@ -107,12 +109,12 @@ struct input_state_machine : input_state_machine_base { message.bytes.clear(); message.timestamp = {}; - state = main; + m_state = main; } bool has_finished_sysex(std::span bytes) const noexcept { - return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7)); + return (((bytes.front() == 0xF0) || (m_state == in_sysex)) && (bytes.back() == 0xF7)); } // Function to process a byte stream which may contain multiple successive @@ -139,22 +141,22 @@ private: void on_bytes_multi_segmented( const message_callback& cb, std::span bytes, int64_t timestamp) { - int64_t nBytes = bytes.size(); - int64_t iByte = 0; + int64_t n_bytes = bytes.size(); + int64_t i_byte = 0; const bool finished_sysex = has_finished_sysex(bytes); - switch (state) + switch (m_state) { case in_sysex: { return on_continue_sysex(cb, bytes, finished_sysex); } case main: { - while (iByte < nBytes) + while (i_byte < n_bytes) { int64_t size = 1; // We are expecting that the next byte in the packet is a status // byte. - const auto status = bytes[iByte]; + const auto status = bytes[i_byte]; if (!(status & 0x80)) break; @@ -170,17 +172,17 @@ private: if (configuration.ignore_sysex) { size = 0; - iByte = nBytes; + i_byte = n_bytes; } else { - size = nBytes - iByte; + size = n_bytes - i_byte; } - if (bytes[nBytes - 1] != 0xF7) + if (bytes[n_bytes - 1] != 0xF7) { // We know per CoreMIDI API there can't be anything else in this packet - state = in_sysex; + m_state = in_sysex; message.assign(bytes.begin(), bytes.begin() + size); message.timestamp = timestamp; return; @@ -192,7 +194,7 @@ private: if (configuration.ignore_timing) { size = 0; - iByte += 2; + i_byte += 2; } else { @@ -209,7 +211,7 @@ private: if (configuration.ignore_timing) { size = 0; - iByte += 1; + i_byte += 1; } else { @@ -222,7 +224,7 @@ private: if (configuration.ignore_sensing) { size = 0; - iByte += 1; + i_byte += 1; } else { @@ -238,14 +240,14 @@ private: // Now process the actual bytes of the message if (size > 0) { - auto begin = bytes.begin() + iByte; + auto begin = bytes.begin() + i_byte; message.assign(begin, begin + size); message.timestamp = timestamp; cb(std::move(message)); message.clear(); - iByte += size; + i_byte += size; } } } @@ -256,7 +258,7 @@ private: const message_callback& cb, std::span bytes, bool finished_sysex) { if (finished_sysex) - state = main; + m_state = main; if (configuration.ignore_sysex) { @@ -283,7 +285,7 @@ private: // SYSEX start case 0xF0: { if (!finished_sysex) - state = in_sysex; + m_state = in_sysex; if (!this->configuration.ignore_sysex) { @@ -328,7 +330,7 @@ private: return; const bool finished_sysex = has_finished_sysex(bytes); - switch (state) + switch (m_state) { case in_sysex: return on_continue_sysex(cb, bytes, finished_sysex); @@ -346,7 +348,7 @@ private: { main, in_sysex - } state{main}; + } m_state{main}; }; } @@ -453,6 +455,18 @@ private: return; break; } + + case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: { + if (this->configuration.midi1_channel_events_to_midi2) + { + libremidi::ump msg; + cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(bytes.data(), msg.data); + msg.timestamp = timestamp; + cb(std::move(msg)); + return; + } + break; + } } libremidi::ump msg; diff --git a/include/libremidi/detail/semaphore.hpp b/include/libremidi/detail/semaphore.hpp index 255c45f..8cc0c56 100644 --- a/include/libremidi/detail/semaphore.hpp +++ b/include/libremidi/detail/semaphore.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include struct semaphore_pair_lock diff --git a/include/libremidi/detail/ump_stream.hpp b/include/libremidi/detail/ump_stream.hpp index 3dd3f2a..70aa5b6 100644 --- a/include/libremidi/detail/ump_stream.hpp +++ b/include/libremidi/detail/ump_stream.hpp @@ -2,7 +2,6 @@ #include #include -#include #include namespace libremidi diff --git a/include/libremidi/error.hpp b/include/libremidi/error.hpp index c038d3f..59e4607 100644 --- a/include/libremidi/error.hpp +++ b/include/libremidi/error.hpp @@ -28,7 +28,7 @@ struct source_location { namespace libremidi { -inline auto from_errc(int ret) noexcept +inline auto from_errc(int64_t ret) noexcept { return static_cast(-ret); } diff --git a/include/libremidi/input_configuration.hpp b/include/libremidi/input_configuration.hpp index 574f02f..5b64974 100644 --- a/include/libremidi/input_configuration.hpp +++ b/include/libremidi/input_configuration.hpp @@ -123,5 +123,10 @@ struct ump_input_configuration uint32_t ignore_sensing : 1 = true; uint32_t timestamps : 3 = timestamp_mode::Absolute; + + //! Upscale MIDI 1 channel events to MIDI 2 channel events. + //! Note that this only has an effect on Windows with MIDI Services + //! as other platforms already do this by default. + uint32_t midi1_channel_events_to_midi2 : 1 = true; }; } diff --git a/include/libremidi/libremidi-c.cpp b/include/libremidi/libremidi-c.cpp index 1413f70..5b76f34 100644 --- a/include/libremidi/libremidi-c.cpp +++ b/include/libremidi/libremidi-c.cpp @@ -44,14 +44,14 @@ const char* libremidi_get_version(void) return LIBREMIDI_VERSION; } -void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +void libremidi_midi1_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) { if (!cb) return; libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); }); } -void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +void libremidi_midi2_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) { if (!cb) return; @@ -276,11 +276,11 @@ int libremidi_midi_in_new( = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; } - if (c->on_midi1_message.callback) + if (c->version == libremidi_midi_configuration::MIDI1 && c->on_midi1_message.callback) conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) { cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size()); }; - else if (c->on_midi1_raw_data.callback) + else if (c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -319,11 +319,11 @@ int libremidi_midi_in_new( = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; } - if (c->on_midi2_message.callback) + if (c->version == libremidi_midi_configuration::MIDI2 && c->on_midi2_message.callback) conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) { cb.callback(cb.context, msg.timestamp, msg.data, msg.size()); }; - else if (c->on_midi2_raw_data.callback) + else if (c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); diff --git a/include/libremidi/libremidi.cpp b/include/libremidi/libremidi.cpp index 3cb79ae..b1c9dc5 100644 --- a/include/libremidi/libremidi.cpp +++ b/include/libremidi/libremidi.cpp @@ -53,14 +53,13 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name) midi2::for_all_backends([&](auto b) { apis.push_back(b.API); }); return apis; } + LIBREMIDI_INLINE -libremidi::API midi_api(const std::any& conf) +libremidi::API midi_api(const input_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::any_cast(&conf) - || std::any_cast(&conf) - || std::any_cast(&conf)) + if (std::get_if(&conf)) { ret = T::API; } @@ -68,9 +67,34 @@ libremidi::API midi_api(const std::any& conf) return ret; } LIBREMIDI_INLINE -std::any midi_in_configuration_for(libremidi::API api) +libremidi::API midi_api(const output_api_configuration& conf) { - std::any ret; + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (std::get_if(&conf)) + { + ret = T::API; + } + }); + return ret; +} +LIBREMIDI_INLINE +libremidi::API midi_api(const observer_api_configuration& conf) +{ + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (std::get_if(&conf)) + { + ret = T::API; + } + }); + return ret; +} + +LIBREMIDI_INLINE +input_api_configuration midi_in_configuration_for(libremidi::API api) +{ + input_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_in_configuration; ret = conf_type{}; @@ -79,9 +103,9 @@ std::any midi_in_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any midi_out_configuration_for(libremidi::API api) +output_api_configuration midi_out_configuration_for(libremidi::API api) { - std::any ret; + output_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_out_configuration; ret = conf_type{}; @@ -90,9 +114,9 @@ std::any midi_out_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any observer_configuration_for(libremidi::API api) +observer_api_configuration observer_configuration_for(libremidi::API api) { - std::any ret; + observer_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_observer_configuration; ret = conf_type{}; @@ -101,13 +125,13 @@ std::any observer_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any midi_in_configuration_for(const libremidi::observer& obs) +input_api_configuration midi_in_configuration_for(const libremidi::observer& obs) { return midi_in_configuration_for(obs.get_current_api()); } LIBREMIDI_INLINE -std::any midi_out_configuration_for(const libremidi::observer& obs) +output_api_configuration midi_out_configuration_for(const libremidi::observer& obs) { // FIXME reuse context when meaningful, e.g. ALSA, JACK... return midi_out_configuration_for(obs.get_current_api()); @@ -144,19 +168,19 @@ catch (const std::exception& e) namespace midi1 { LIBREMIDI_INLINE -std::any in_default_configuration() +input_api_configuration in_default_configuration() { return midi_in_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any out_default_configuration() +output_api_configuration out_default_configuration() { return midi_out_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any observer_default_configuration() +observer_api_configuration observer_default_configuration() { return observer_configuration_for(default_api()); } @@ -165,19 +189,19 @@ std::any observer_default_configuration() namespace midi2 { LIBREMIDI_INLINE -std::any in_default_configuration() +input_api_configuration in_default_configuration() { return midi_in_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any out_default_configuration() +output_api_configuration out_default_configuration() { return midi_out_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any observer_default_configuration() +observer_api_configuration observer_default_configuration() { return observer_configuration_for(default_api()); } diff --git a/include/libremidi/libremidi.hpp b/include/libremidi/libremidi.hpp index 4085980..da95ba7 100644 --- a/include/libremidi/libremidi.hpp +++ b/include/libremidi/libremidi.hpp @@ -58,6 +58,7 @@ */ #include +#include #include #include #include @@ -68,7 +69,6 @@ #include #include #endif -#include namespace libremidi { @@ -84,7 +84,7 @@ public: //! such as jack_observer_configuration, winmm_observer_configuration, etc... //! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary explicit observer(const observer_configuration& conf = {}) noexcept; - explicit observer(observer_configuration conf, std::any api_conf); + explicit observer(const observer_configuration& conf, observer_api_configuration api_conf); observer(const observer&) = delete; observer(observer&& other) noexcept; observer& operator=(const observer&) = delete; @@ -98,7 +98,7 @@ public: [[nodiscard]] std::vector get_output_ports() const noexcept; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; //! Main class for receiving MIDI 1.0 and 2.0 messages. @@ -111,15 +111,15 @@ public: //! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_in(input_configuration conf, std::any api_conf); + explicit midi_in(const input_configuration& conf, const input_api_configuration& api_conf); //! Construct a midi_in object with the default MIDI 2 back-end for the platform - explicit midi_in(ump_input_configuration conf) noexcept; + explicit midi_in(const ump_input_configuration& conf) noexcept; //! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_in(ump_input_configuration conf, std::any api_conf); + explicit midi_in(const ump_input_configuration& conf, const input_api_configuration& api_conf); midi_in(const midi_in&) = delete; midi_in(midi_in&& other) noexcept; @@ -157,7 +157,7 @@ public: timestamp absolute_timestamp() const noexcept; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; //! Main class for sending MIDI 1.0 and 2.0 messages. @@ -170,7 +170,7 @@ public: //! Construct a midi_out object with a configuration object for a specific back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_out(output_configuration conf, std::any api_conf); + explicit midi_out(const output_configuration& conf, const output_api_configuration& api_conf); midi_out(const midi_out&) = delete; midi_out(midi_out&& other) noexcept; @@ -260,7 +260,7 @@ public: stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; } diff --git a/include/libremidi/message.hpp b/include/libremidi/message.hpp index da8366e..cc35516 100644 --- a/include/libremidi/message.hpp +++ b/include/libremidi/message.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #if defined(__cpp_exceptions) @@ -121,12 +122,8 @@ struct message auto rbegin() noexcept { return bytes.rbegin(); } auto rend() noexcept { return bytes.rend(); } - bool uses_channel(int channel) const + bool uses_channel(int channel) const LIBREMIDI_PRECONDITION(channel > 0 && channel <= 16) { -#if defined(__cpp_exceptions) - if (channel <= 0 || channel > 16) - throw std::range_error("out of range"); -#endif return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0); } @@ -250,12 +247,12 @@ struct meta_events int notated_32nd_notes_per_beat = 96) { int n = 1; - int powTwo = 0; + int pow_two = 0; while (n < denominator) { n <<= 1; - ++powTwo; + ++pow_two; } return { @@ -263,7 +260,7 @@ struct meta_events 0x58, 0x04, static_cast(numerator), - static_cast(powTwo), + static_cast(pow_two), static_cast(clocks_per_click), static_cast(notated_32nd_notes_per_beat)}; } diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index 29ba76a..322ba31 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -61,14 +61,14 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c } static LIBREMIDI_INLINE std::unique_ptr -make_midi_in(auto base_conf, std::any api_conf, auto backends) +make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) { std::unique_ptr ptr; assert(base_conf.on_message || base_conf.on_raw_data); auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = std::get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -117,8 +117,9 @@ make_midi1_in(const input_configuration& base_conf) return std::make_unique(input_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr -make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +static LIBREMIDI_INLINE std::unique_ptr make_midi1_in( + const input_configuration& base_conf, const input_api_configuration& api_conf, + libremidi::API api) { if (libremidi::is_midi1(api)) { @@ -133,13 +134,13 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, li } static LIBREMIDI_INLINE std::unique_ptr -make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) +make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (std::get_if(&api_conf)) { return make_midi1_in(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = std::get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -161,19 +162,19 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) /// MIDI 1 constructors LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept - : impl_{make_midi1_in(base_conf)} + : m_impl{make_midi1_in(base_conf)} { } LIBREMIDI_INLINE -midi_in::midi_in(input_configuration base_conf, std::any api_conf) - : impl_{make_midi1_in(base_conf, api_conf)} +midi_in::midi_in(const input_configuration& base_conf, const input_api_configuration& api_conf) + : m_impl{make_midi1_in(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } } @@ -214,7 +215,8 @@ make_midi2_in(const ump_input_configuration& base_conf) } static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( - const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api) + const ump_input_configuration& base_conf, const input_api_configuration& api_conf, + libremidi::API api) { if (is_midi2(api)) { @@ -230,13 +232,13 @@ static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( } static LIBREMIDI_INLINE std::unique_ptr -make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf) +make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (std::get_if(&api_conf)) { return make_midi2_in(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = std::get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -257,45 +259,45 @@ make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf } /// MIDI 2 constructors -LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept - : impl_{make_midi2_in(base_conf)} +LIBREMIDI_INLINE midi_in::midi_in(const ump_input_configuration& base_conf) noexcept + : m_impl{make_midi2_in(base_conf)} { } LIBREMIDI_INLINE -midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf) - : impl_{make_midi2_in(base_conf, api_conf)} +midi_in::midi_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) + : m_impl{make_midi2_in(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE midi_in::~midi_in() = default; LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ + other.m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE stdx::error midi_in::set_port_name(std::string_view portName) { - if(impl_->is_port_open()) - return impl_->set_port_name(portName); + if (m_impl->is_port_open()) + return m_impl->set_port_name(portName); return std::errc::not_connected; } LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); return *this; } @@ -303,23 +305,23 @@ LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept LIBREMIDI_INLINE libremidi::API midi_in::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE stdx::error midi_in::open_port(const input_port& port, std::string_view portName) { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_port(port, portName); + auto ret = m_impl->open_port(port, portName); if (ret == stdx::error{}) { - impl_->connected_ = true; - impl_->port_open_ = true; + m_impl->connected_ = true; + m_impl->port_open_ = true; } return ret; } @@ -327,28 +329,28 @@ stdx::error midi_in::open_port(const input_port& port, std::string_view portName LIBREMIDI_INLINE stdx::error midi_in::open_virtual_port(std::string_view portName) { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_virtual_port(portName); + auto ret = m_impl->open_virtual_port(portName); if (ret == stdx::error{}) - impl_->port_open_ = true; + m_impl->port_open_ = true; return ret; } LIBREMIDI_INLINE stdx::error midi_in::close_port() { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - auto ret = impl_->close_port(); + auto ret = m_impl->close_port(); - impl_->connected_ = false; - impl_->port_open_ = false; + m_impl->connected_ = false; + m_impl->port_open_ = false; return ret; } @@ -356,18 +358,18 @@ stdx::error midi_in::close_port() LIBREMIDI_INLINE bool midi_in::is_port_open() const noexcept { - return impl_->is_port_open(); + return m_impl->is_port_open(); } LIBREMIDI_INLINE bool midi_in::is_port_connected() const noexcept { - return impl_->is_port_connected(); + return m_impl->is_port_connected(); } LIBREMIDI_INLINE int64_t midi_in::absolute_timestamp() const noexcept { - return impl_->absolute_timestamp(); + return m_impl->absolute_timestamp(); } } diff --git a/include/libremidi/midi_out.cpp b/include/libremidi/midi_out.cpp index 82b3b7a..d26bfb8 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -11,11 +11,11 @@ namespace libremidi { static LIBREMIDI_INLINE std::unique_ptr -make_midi_out_impl(auto base_conf, std::any api_conf) +make_midi_out_impl(auto base_conf, output_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = std::get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -23,7 +23,8 @@ make_midi_out_impl(auto base_conf, std::any api_conf) return false; }; std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends); - std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); + if (!ptr) + std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); return ptr; } @@ -58,13 +59,13 @@ make_midi_out(const output_configuration& base_conf) } static LIBREMIDI_INLINE std::unique_ptr -make_midi_out(const output_configuration& base_conf, const std::any& api_conf) +make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (std::get_if(&api_conf)) { return make_midi_out(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = std::get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -85,35 +86,35 @@ make_midi_out(const output_configuration& base_conf, const std::any& api_conf) } LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept - : impl_{make_midi_out(base_conf)} + : m_impl{make_midi_out(base_conf)} { } LIBREMIDI_INLINE -midi_out::midi_out(output_configuration base_conf, std::any api_conf) - : impl_{make_midi_out(base_conf, api_conf)} +midi_out::midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) + : m_impl{make_midi_out(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi out for the given api"); - impl_ = std::make_unique(output_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE midi_out::~midi_out() = default; LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ + other.m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); return *this; } @@ -121,8 +122,8 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept LIBREMIDI_INLINE stdx::error midi_out::set_port_name(std::string_view portName) const { - if(impl_->is_port_open()) - return impl_->set_port_name(portName); + if (m_impl->is_port_open()) + return m_impl->set_port_name(portName); return std::errc::not_connected; } @@ -130,23 +131,23 @@ stdx::error midi_out::set_port_name(std::string_view portName) const LIBREMIDI_INLINE libremidi::API midi_out::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_port(port, portName); + auto ret = m_impl->open_port(port, portName); if (ret == stdx::error{}) { - impl_->connected_ = true; - impl_->port_open_ = true; + m_impl->connected_ = true; + m_impl->port_open_ = true; } return ret; } @@ -154,40 +155,40 @@ stdx::error midi_out::open_port(const output_port& port, std::string_view portNa LIBREMIDI_INLINE stdx::error midi_out::open_virtual_port(std::string_view portName) const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_virtual_port(portName); + auto ret = m_impl->open_virtual_port(portName); if (ret == stdx::error{}) - impl_->port_open_ = true; + m_impl->port_open_ = true; return ret; } LIBREMIDI_INLINE stdx::error midi_out::close_port() const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - auto ret = impl_->close_port(); - impl_->connected_ = false; - impl_->port_open_ = false; + auto ret = m_impl->close_port(); + m_impl->connected_ = false; + m_impl->port_open_ = false; return ret; } LIBREMIDI_INLINE bool midi_out::is_port_open() const noexcept { - return impl_->is_port_open(); + return m_impl->is_port_open(); } LIBREMIDI_INLINE bool midi_out::is_port_connected() const noexcept { - return impl_->is_port_connected(); + return m_impl->is_port_connected(); } LIBREMIDI_INLINE @@ -227,13 +228,13 @@ stdx::error midi_out::send_message(const unsigned char* message, size_t size) co assert(size > 0); #endif - return impl_->send_message(message, size); + return m_impl->send_message(message, size); } LIBREMIDI_INLINE int64_t midi_out::current_time() { - return impl_->current_time(); + return m_impl->current_time(); } LIBREMIDI_INLINE @@ -243,7 +244,7 @@ stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, assert(size > 0); #endif - return impl_->schedule_message(ts, message, size); + return m_impl->schedule_message(ts, message, size); } LIBREMIDI_INLINE @@ -254,7 +255,7 @@ stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const assert(size <= 4); #endif - return impl_->send_ump(message, size); + return m_impl->send_ump(message, size); } LIBREMIDI_INLINE stdx::error midi_out::send_ump(const libremidi::ump& message) const @@ -321,6 +322,6 @@ stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t s assert(size > 0); #endif - return impl_->schedule_ump(ts, message, size); + return m_impl->schedule_ump(ts, message, size); } } diff --git a/include/libremidi/observer.cpp b/include/libremidi/observer.cpp index 442cffb..81abe3a 100644 --- a/include/libremidi/observer.cpp +++ b/include/libremidi/observer.cpp @@ -5,17 +5,19 @@ #include #include +#include + namespace libremidi { -static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf) +static LIBREMIDI_INLINE std::unique_ptr make_observer(const auto& base_conf) { for (const auto& api : available_apis()) { try { - if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) - return impl_; + if (auto impl = make_observer(base_conf, observer_configuration_for(api))) + return impl; } catch (const std::exception& e) { @@ -26,8 +28,8 @@ static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_co { try { - if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) - return impl_; + if (auto impl = make_observer(base_conf, observer_configuration_for(api))) + return impl; } catch (const std::exception& e) { @@ -37,11 +39,11 @@ static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_co return std::make_unique(observer_configuration{}, dummy_configuration{}); } -LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) +LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = std::get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -49,17 +51,19 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) return false; }; std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends); - std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); + if (!ptr) + std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); return ptr; } -LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std::any api_conf) +LIBREMIDI_INLINE std::unique_ptr +make_observer(const auto& base_conf, observer_api_configuration api_conf) { - if (!api_conf.has_value()) + if (std::get_if(&api_conf)) { return make_observer(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = std::get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -80,32 +84,33 @@ LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std } LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept - : impl_{make_observer(base_conf)} + : m_impl{make_observer(base_conf)} { } -LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf) - : impl_{make_observer(base_conf, api_conf)} +LIBREMIDI_INLINE +observer::observer(const observer_configuration& base_conf, observer_api_configuration api_conf) + : m_impl{make_observer(base_conf, std::move(api_conf))} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open observer for the given api"); - impl_ = std::make_unique(observer_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(observer_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE observer::observer(observer&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ = std::make_unique( + other.m_impl = std::make_unique( observer_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ = std::make_unique( + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique( observer_configuration{}, dummy_configuration{}); return *this; } @@ -116,18 +121,18 @@ observer::~observer() = default; LIBREMIDI_INLINE libremidi::API observer::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE std::vector observer::get_input_ports() const noexcept { - return impl_->get_input_ports(); + return m_impl->get_input_ports(); } LIBREMIDI_INLINE std::vector observer::get_output_ports() const noexcept { - return impl_->get_output_ports(); + return m_impl->get_output_ports(); } } diff --git a/include/libremidi/observer_configuration.hpp b/include/libremidi/observer_configuration.hpp index 0e427a3..891cb28 100644 --- a/include/libremidi/observer_configuration.hpp +++ b/include/libremidi/observer_configuration.hpp @@ -2,43 +2,106 @@ #include #include +#include #include #include +#include namespace libremidi { using client_handle = std::uint64_t; using port_handle = std::uint64_t; +struct uuid +{ + std::array bytes; + + bool operator==(const uuid& other) const noexcept = default; + std::strong_ordering operator<=>(const uuid& other) const noexcept = default; +}; +using container_identifier = std::variant; +using device_identifier = std::variant; + struct LIBREMIDI_EXPORT port_information { - // Handle to the client object: + enum port_type : uint8_t + { + unknown = 0, + software = (1 << 1), + loopback = (1 << 2), + + hardware = (1 << 3), + usb = (1 << 4), + bluetooth = (1 << 5), + pci = (1 << 6), + + network = (1 << 7), + }; + + /// Handle to the API client object if the API provides one // ALSA Raw: unused // ALSA Seq: snd_seq_t* // CoreMIDI: MidiClientRef // WebMIDI: unused // JACK: jack_client_t* - // PipeWire: unused + // PipeWire: unused // FIXME: pw_context? pw_main_loop? // WinMM: unused // WinUWP: unused client_handle client = static_cast(-1); - // ALSA Raw: { uint16_t card, device, sub, padding; } - // ALSA Seq: { uint32_t client, uint32_t port; } + /// Container identifier if the API provides one + // WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ; + // this is not the string but the binary representation). + // ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0" + // CoreMIDI: USBLocationID (int32_t) + container_identifier container = std::monostate{}; + + /// Device identifier if the API provides one + // WinMM: { uint16_t manufacturer_id, uint16_t product_id; } + // WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..." + // ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0" + // CoreMIDI: USBVendorProduct (int32_t) + device_identifier device = std::monostate{}; + + /// Handle to the port identifier if the API provides one + // ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }. + // ALSA Seq: bit_cast to struct { uint32_t client, port; } // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) // WebMIDI: unused // JACK: jack_port_id_t // PipeWire: port.id - // WinMM: unset, identified by port_name + // WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based) + // WinMM: port index between 0 and midi{In,Out}GetNumDevs() // WinUWP: unused port_handle port = static_cast(-1); + /// User-readable information + // CoreMIDI: kMIDIPropertyManufacturer + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName + // WinMM: unavailable std::string manufacturer{}; + + // CoreMIDI: kMIDIPropertyModel + // WinMIDI: MidiEndpointDeviceInformation::Name + // WinMM: unavailable std::string device_name{}; + + // CoreMIDI: kMIDIPropertyName + // WinMIDI: MidiGroupTerminalBlock::Name + // WinMM: szPname std::string port_name{}; + + // CoreMIDI: kMIDIPropertyDisplayName + // Otherwise: the closest to a unique name we can get std::string display_name{}; + /// Port type + // CoreMIDI: available + // WinMM: unavailable + // WinMIDI: available + port_type type = port_type::unknown; + bool operator==(const port_information& other) const noexcept = default; std::strong_ordering operator<=>(const port_information& other) const noexcept = default; }; diff --git a/include/libremidi/protocols/remote_control.hpp b/include/libremidi/protocols/remote_control.hpp new file mode 100644 index 0000000..9ce2a1d --- /dev/null +++ b/include/libremidi/protocols/remote_control.hpp @@ -0,0 +1,858 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace libremidi +{ +static constexpr auto to_underlying(auto e) +{ + return static_cast>(e); +} + +// A clean-room reverse-engineered remote control protocol compatible with many hardware devices. +// Thanks https://github.com/NicoG60/TouchMCU ! +struct remote_control_protocol +{ + enum class device_type : uint8_t + { + logic_control = 0x10, + logic_control_xt = 0x11, + mackie_control = 0x14 + }; + + enum class command_to_device : uint8_t + { + device_query = 0x00, + host_connection_reply = 0x02, + + transport_click = 0x0A, + lcd_backlight_save = 0x0B, + touchless_movable_fader = 0x0C, + + faders_touch_sensitivity = 0x0E, + go_offline = 0x0F, + update_tc_display = 0x10, + update_assignment_display = 0x11, + update_lcd = 0x12, + firmware_version_request = 0x13, + version_reply = 0x14, + + firmware_update = 0x18, + + channel_meter_mode = 0x20, + global_lcd_meter_mode = 0x21, + + faders_to_minimum = 0x61, + all_leds_off = 0x62, + reset = 0x63, + }; + + enum class command_from_device : uint8_t + { + host_connection_query = 0x01, + host_connection_confirmation = 0x03, + host_connection_error = 0x04, + version_reply = 0x14, + }; + + enum class lcd_meter_mode : uint8_t + { + horizontal = 0x00, + vertical = 0x01, + }; + + enum class fader_sensitivity : uint8_t + { + sensitivity_0 = 0x00, + sensitivity_1 = 0x01, + sensitivity_2 = 0x02, + sensitivity_3 = 0x03, + sensitivity_4 = 0x04, + + sensitivity_default = sensitivity_3, + }; + + // CC Message: + // 0b0LMMVVVV + // L: toggle underneath LED + // MM: mode as led_ring_mode + // VVVV: value + enum class led_ring_mode : uint8_t + { + mode_0 = 0b00, // one led only + mode_1 = 0b01, // pan pot + mode_2 = 0b10, // fill leds from left + mode_3 = 0b11, // fill leds from middle + }; + + enum class pot : uint8_t + { + pot_0 = 0x00, + pot_1 = 0x01, + pot_2 = 0x02, + pot_3 = 0x03, + pot_4 = 0x04, + pot_5 = 0x05, + pot_6 = 0x06, + pot_7 = 0x07, + }; + + enum class fader : uint8_t + { + fader_0 = 0x00, + fader_1 = 0x01, + fader_2 = 0x02, + fader_3 = 0x03, + fader_4 = 0x04, + fader_5 = 0x05, + fader_6 = 0x06, + fader_7 = 0x07, + fader_master = 0x08, + }; + + // control changes + enum class mixer_control : uint8_t + { + // rotation: CC + // 0b00000001 : clockwise + // 0b01000001 : counter-clockwise + vpot_rotation_0 = 0x10 + 0x00, + vpot_rotation_1 = 0x10 + 0x01, + vpot_rotation_2 = 0x10 + 0x02, + vpot_rotation_3 = 0x10 + 0x03, + vpot_rotation_4 = 0x10 + 0x04, + vpot_rotation_5 = 0x10 + 0x05, + vpot_rotation_6 = 0x10 + 0x06, + vpot_rotation_7 = 0x10 + 0x07, + + external_control = 0x2E, + + // led ring: CC + vpot_led_0 = 0x30 + 0x00, + vpot_led_1 = 0x30 + 0x01, + vpot_led_2 = 0x30 + 0x02, + vpot_led_3 = 0x30 + 0x03, + vpot_led_4 = 0x30 + 0x04, + vpot_led_5 = 0x30 + 0x05, + vpot_led_6 = 0x30 + 0x06, + vpot_led_7 = 0x30 + 0x07, + + jog_wheel = 0x3C, + + timecode_digit_0 = 0x40 + 0x00, + timecode_digit_1 = 0x40 + 0x01, + timecode_digit_2 = 0x40 + 0x02, + timecode_digit_3 = 0x40 + 0x03, + timecode_digit_4 = 0x40 + 0x04, + timecode_digit_5 = 0x40 + 0x05, + timecode_digit_6 = 0x40 + 0x06, + timecode_digit_7 = 0x40 + 0x07, + timecode_digit_8 = 0x40 + 0x08, + timecode_digit_9 = 0x40 + 0x09, + + assignment_digit_0 = 0x4A, + assignment_digit_1 = 0x4B, + }; + + // note events + enum class mixer_command : uint8_t + { + vpot_click_0 = 0x20 + 0x00, + vpot_click_1 = 0x20 + 0x01, + vpot_click_2 = 0x20 + 0x02, + vpot_click_3 = 0x20 + 0x03, + vpot_click_4 = 0x20 + 0x04, + vpot_click_5 = 0x20 + 0x05, + vpot_click_6 = 0x20 + 0x06, + vpot_click_7 = 0x20 + 0x07, + + rec_0 = 0x00 + 0x00, + rec_1 = 0x00 + 0x01, + rec_2 = 0x00 + 0x02, + rec_3 = 0x00 + 0x03, + rec_4 = 0x00 + 0x04, + rec_5 = 0x00 + 0x05, + rec_6 = 0x00 + 0x06, + rec_7 = 0x00 + 0x07, + + solo_0 = 0x08 + 0x00, + solo_1 = 0x08 + 0x01, + solo_2 = 0x08 + 0x02, + solo_3 = 0x08 + 0x03, + solo_4 = 0x08 + 0x04, + solo_5 = 0x08 + 0x05, + solo_6 = 0x08 + 0x06, + solo_7 = 0x08 + 0x07, + + mute_0 = 0x10 + 0x00, + mute_1 = 0x10 + 0x01, + mute_2 = 0x10 + 0x02, + mute_3 = 0x10 + 0x03, + mute_4 = 0x10 + 0x04, + mute_5 = 0x10 + 0x05, + mute_6 = 0x10 + 0x06, + mute_7 = 0x10 + 0x07, + + sel_0 = 0x18 + 0x00, + sel_1 = 0x18 + 0x01, + sel_2 = 0x18 + 0x02, + sel_3 = 0x18 + 0x03, + sel_4 = 0x18 + 0x04, + sel_5 = 0x18 + 0x05, + sel_6 = 0x18 + 0x06, + sel_7 = 0x18 + 0x07, + + // TODO metering + assign_track = 0x28, + assign_send = 0x29, + assign_pan = 0x2A, + assign_plugin = 0x2B, + assign_eq = 0x2C, + assign_instrument = 0x2D, + + bank_left = 0x2E, + bank_right = 0x2F, + channel_left = 0x30, + channel_right = 0x31, + flip = 0x32, + global = 0x33, + + name_value_button = 0x34, + smpte_beats_button = 0x35, + + f1 = 0x36 + 0x00, + f2 = 0x36 + 0x01, + f3 = 0x36 + 0x02, + f4 = 0x36 + 0x03, + f5 = 0x36 + 0x04, + f6 = 0x36 + 0x05, + f7 = 0x36 + 0x06, + f8 = 0x36 + 0x07, + + midi_tracks = 0x3E, + inputs = 0x3F, + audio_tracks = 0x40, + audio_instruments = 0x41, + aux = 0x42, + busses = 0x43, + outputs = 0x44, + user = 0x45, + + shift = 0x46, + option = 0x47, + control = 0x48, + alt = 0x49, + + save = 0x50, + undo = 0x51, + cancel = 0x52, + enter = 0x53, + + markers = 0x54, + nudge = 0x55, + cycle = 0x56, + drop = 0x57, + replace = 0x58, + click = 0x59, + solo = 0x5a, + + rewind = 0x5b, + forward = 0x5c, + stop = 0x5d, + play = 0x5e, + record = 0x5f, + + up = 0x60, + down = 0x61, + left = 0x62, + right = 0x63, + zoom = 0x64, + scrub = 0x65, + + user_switch_1 = 0x66, + user_switch_2 = 0x67, + + fader_touched_0 = 0x68, + fader_touched_1 = 0x69, + fader_touched_2 = 0x6a, + fader_touched_3 = 0x6b, + fader_touched_4 = 0x6c, + fader_touched_5 = 0x6d, + fader_touched_6 = 0x6e, + fader_touched_7 = 0x6f, + fader_touched_master = 0x70, + + smpte_led = 0x71, + beats_led = 0x72, + rude_solo_led = 0x73, + + relay_click = 0x76, + }; + + template + using arr = std::array; + + device_type type = device_type::mackie_control; + + static libremidi::message make_command_impl(auto&&... data) + { + using namespace std; + libremidi::message m; + m.bytes.reserve((std::ssize(data) + ...)); + (m.bytes.insert(m.bytes.end(), begin(data), end(data)), ...); + return m; + } + + libremidi::message make_command(command_to_device c, auto&&... data) + { + using namespace std; + const auto type = to_underlying(this->type); + const auto cmd = to_underlying(c); + const uint8_t header[6]{0xF0, 0x00, 0x00, 0x66, type, cmd}; + const uint8_t footer[1]{0xF7}; + return make_command_impl(header, data..., footer); + } + + auto device_query() { return make_command(command_to_device::device_query); } + + auto response_to_challenge(arr<4> c) + { + arr<4> r; + + r[0] = 0x7F & (c[0] + (c[1] ^ 0x0A) - c[3]); + r[1] = 0x7F & ((c[2] >> 4) ^ (c[0] + c[3])); + r[2] = 0x7F & (c[3] - (c[2] << 2) ^ (c[0] | c[1])); + r[3] = 0x7F & (c[1] - c[2] + (0xF0 ^ (c[3] << 4))); + + return r; + } + + auto host_connection_reply(arr<7> serial, arr<4> challenge_code) + { + const auto res = response_to_challenge(challenge_code); + return make_command(command_to_device::host_connection_reply, serial, res); + } + + auto transport_click(bool enabled) + { + return make_command( + command_to_device::transport_click, arr<1>{uint8_t(enabled ? 0x01 : 0x00)}); + } + + auto lcd_backlight_save(uint8_t timeout) + { + // 0: instant off otherwise timeout in minutes + return make_command(command_to_device::lcd_backlight_save, arr<1>{timeout}); + } + + auto touchless_movable_fader(bool enabled) + { + return make_command( + command_to_device::touchless_movable_fader, arr<1>{uint8_t(enabled ? 0x01 : 0x00)}); + } + + auto faders_touch_sensitivity(uint8_t fader_id, fader_sensitivity sens) + { + return make_command( + command_to_device::faders_touch_sensitivity, arr<2>{fader_id, to_underlying(sens)}); + } + + auto go_offline() { return make_command(command_to_device::go_offline, arr<1>{0x7F}); } + + auto update_tc_display() + { + // FIXME 1 .. 10 + return make_command(command_to_device::update_tc_display, arr<10>{}); + } + + auto update_assignment_display() + { + // FIXME 1 .. 2 + return make_command(command_to_device::update_assignment_display, arr<2>{}); + } + + auto update_lcd(std::string_view txt, int pos) + { + // FIXME + if (pos < 0 || pos >= 112) + return libremidi::message{}; + + int len = int(std::ssize(txt)); + + if (len > (112 - pos)) + { + txt = txt.substr(0, 112 - pos); + len = 112 - pos; + } + + uint8_t buf[128]; + const int N = std::min(len, 112 - pos); + for (int i = 0; i < N; i++) + { + buf[i + pos] = charmap_lcd(txt[i]); + } + buf[55] = '\n'; + buf[111] = '\n'; + + uint8_t cmd_pos = pos; + + return make_command(command_to_device::update_lcd, arr<1>{cmd_pos}, std::span(buf + pos, len)); + } + + auto update_lcd(std::string_view txt) + { + uint8_t buf[112] = {}; + for (int i = 0; i < std::min(int(std::ssize(txt)), 112); i++) + { + buf[i] = charmap_lcd(txt[i]); + } + buf[55] = '\n'; + buf[111] = '\n'; + return make_command(command_to_device::update_lcd, arr<1>{0}, std::span(buf, 112)); + } + + auto firmware_version_request() + { + return make_command(command_to_device::firmware_version_request, arr<1>{0}); + } + + auto firmware_update(std::span firmware) + { + return make_command(command_to_device::firmware_update, firmware); + } + + auto channel_meter_mode(uint8_t fader_id, bool level_meter, bool peak_hold, bool signal_led) + { + uint8_t mode = 0; + + if (signal_led) + mode |= 0b1; + if (peak_hold) + mode |= 0b10; + if (level_meter) + mode |= 0b100; + + return make_command(command_to_device::channel_meter_mode, arr<2>{fader_id, mode}); + } + + auto global_lcd_meter_mode(lcd_meter_mode mode) + { + return make_command(command_to_device::global_lcd_meter_mode, arr<1>{to_underlying(mode)}); + } + + auto faders_to_minimum() { return make_command(command_to_device::faders_to_minimum); } + + auto all_leds_off() { return make_command(command_to_device::all_leds_off); } + + auto reset() { return make_command(command_to_device::reset); } + + static auto timecode(int hi, int mi, int si, int framei) + { + std::vector msg; + auto h = std::to_string(hi); + while (h.size() < 3) + h.insert(h.begin(), '0'); + auto m = std::to_string(mi); + while (m.size() < 2) + m.insert(m.begin(), '0'); + auto s = std::to_string(si); + while (s.size() < 2) + s.insert(s.begin(), '0'); + auto f = std::to_string(framei); + while (f.size() < 3) + f.insert(f.begin(), '0'); + + using ce = libremidi::channel_events; + msg.push_back(ce::control_change(1, 0x49, charmap_7segment(h[0]))); + msg.push_back(ce::control_change(1, 0x48, charmap_7segment(h[1]))); + msg.push_back(ce::control_change(1, 0x47, charmap_7segment(h[2]))); + + msg.push_back(ce::control_change(1, 0x46, charmap_7segment(m[0]))); + msg.push_back(ce::control_change(1, 0x45, charmap_7segment(m[1]))); + + msg.push_back(ce::control_change(1, 0x44, charmap_7segment(s[0]))); + msg.push_back(ce::control_change(1, 0x43, charmap_7segment(s[1]))); + + msg.push_back(ce::control_change(1, 0x42, charmap_7segment(f[0]))); + msg.push_back(ce::control_change(1, 0x41, charmap_7segment(f[1]))); + msg.push_back(ce::control_change(1, 0x40, charmap_7segment(f[2]))); + + return msg; + } + + static uint8_t charmap_7segment(char c, bool dot) + { + uint8_t res = charmap_7segment(c); + if (dot) + res |= 0b00100000; + return res; + }; + + static uint8_t charmap_7segment(char c) + { + // FIXME there are some more characters but what to map them to ? :) + if (c >= 'a' && c <= 'z') + return c - 'a' + 1; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 1; + else if (c >= '0' && c <= '9') + return c - '0' + 0x30; + else + switch (c) + { + case '[': + case '{': + return 0x1B; + case '\\': + case '~': // yen too ? + return 0x1C; + case ']': + case '}': + return 0x1D; + case '^': + return 0x1E; + case '_': + return 0x1F; + + case '!': + return 0x21; + case '"': + return 0x22; + case '#': + return 0x23; + case '$': + return 0x24; + case '%': + return 0x25; + case '&': + return 0x26; + case '\'': + return 0x27; + case '(': + return 0x28; + case ')': + return 0x29; + case '*': + return 0x2A; + case '+': + return 0x2B; + case ',': + return 0x2C; + case '-': + return 0x2D; + case '.': + return 0x2E; + case '/': + return 0x2F; + + case ':': + return 0x3A; + case ';': + return 0x3B; + case '<': + return 0x3C; + case '=': + return 0x3D; + case '>': + return 0x3E; + case '?': + return 0x3F; + + default: + return 0x00; + } + } + static uint8_t charmap_lcd(char c) + { + // FIXME there are some more characters but what to map them to ? :) + if (c >= 'a' && c <= 'z') + return c - 'a' + 0x61; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 0x41; + else if (c >= '0' && c <= '9') + return c - '0' + 0x30; + else + switch (c) + { + case '!': + return 0x21; + case '"': + return 0x22; + case '#': + return 0x23; + case '$': + return 0x24; + case '%': + return 0x25; + case '&': + return 0x26; + case '\'': + return 0x27; + case '(': + return 0x28; + case ')': + return 0x29; + case '*': + return 0x2A; + case '+': + return 0x2B; + case ',': + return 0x2C; + case '-': + return 0x2D; + case '.': + return 0x2E; + case '/': + return 0x2F; + + case ':': + return 0x3A; + case ';': + return 0x3B; + case '<': + return 0x3C; + case '=': + return 0x3D; + case '>': + return 0x3E; + case '?': + return 0x3F; + + case '@': + return 0x40; + case '[': + return 0x5B; + case '~': // Yen symbol... builtin mojibake? + return 0x5C; + case ']': + return 0x5D; + case '^': + return 0x5E; + case '_': + return 0x5F; + case '`': + return 0x60; + case '{': + return 0x7B; + case '|': + return 0x7C; + case '}': + return 0x7D; + case '\u000E': + return 0x7E; + case '\u000F': + return 0x7F; + default: + return c; // gives access to the bubble first row 0x00 > 0x0F + } + } +}; + +struct rcp_configuration +{ + //! How to send MIDI messages to the device. + //! Note: this function *will* be called from different thread, + //! thus it has to be thread-safe, for instance + //! by storing the message in an event queue. + std::function midi_out; + + std::function on_connected; + std::function on_command; + std::function on_control; + std::function on_fader; + + libremidi::midi_error_callback on_error{}; +}; + +struct remote_control_processor : libremidi::error_handler +{ + using rcp = libremidi::remote_control_protocol; + rcp_configuration configuration; + rcp impl; + + explicit remote_control_processor(rcp_configuration conf) + : configuration{std::move(conf)} + { + assert(configuration.midi_out); + + if (!configuration.on_error) + configuration.on_error = [](std::string_view s, auto&&...) { + std::fprintf(stderr, "libremidi: rcp error: %s\n", s.data()); + }; + + if (!configuration.on_connected) + configuration.on_connected + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_connected"); }; + + if (!configuration.on_command) + configuration.on_command + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_command"); }; + + if (!configuration.on_control) + configuration.on_control + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_control"); }; + + if (!configuration.on_fader) + configuration.on_fader + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_fader"); }; + } + + void start() + { + current_state = waiting_for_query; + configuration.midi_out(impl.device_query()); + } + + void on_midi(const libremidi::message& message) + { + switch (message.get_message_type()) + { + case libremidi::message_type::SYSTEM_EXCLUSIVE: + if (auto N = message.size(); N >= 7) + { + const uint8_t* bytes = message.bytes.data(); + + // strip 0xF0 & 0xF7 + bytes += 1; + N -= 2; + + // Mackie manufacturer ID check + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x66) + { + impl.type = static_cast(bytes[3]); + + // strip header + bytes += 4; + N -= 4; + on_rcp_command(std::span(bytes, N)); + } + } + else + { + libremidi_handle_error(configuration, "Invalid sysex"); + } + break; + case libremidi::message_type::NOTE_ON: + configuration.on_command(static_cast(message[1]), message[2] > 0); + break; + case libremidi::message_type::NOTE_OFF: + break; + case libremidi::message_type::CONTROL_CHANGE: + configuration.on_control(static_cast(message[1]), message[2]); + break; + case libremidi::message_type::PITCH_BEND: { + uint16_t value = message.bytes[2] * 128 + message.bytes[1]; + configuration.on_fader(static_cast(uint8_t(message.get_channel() - 1)), value); + break; + } + default: + break; + } + } + + void on_rcp_command(std::span cmd) + { + if (cmd.empty()) + { + libremidi_handle_error(configuration, "on_rcp_command: empty command"); + return; + } + + auto command = static_cast(cmd[0]); + cmd = cmd.subspan(1); + switch (command) + { + case rcp::command_from_device::host_connection_query: { + if (cmd.size() == 11) + { + current_state = got_query; + + std::array serial; + std::array challenge; + std::copy_n(cmd.data(), 7, serial.begin()); + std::copy_n(cmd.data() + 7, 4, challenge.begin()); + + configuration.midi_out(impl.host_connection_reply(serial, challenge)); + } + else + libremidi_handle_error(configuration, "host_connection_query: invalid size"); + break; + } + case rcp::command_from_device::host_connection_confirmation: + current_state = connected; + configuration.on_connected(impl.type); + break; + case rcp::command_from_device::host_connection_error: + current_state = errored; + libremidi_handle_error(configuration, "host_connection_error"); + break; + case rcp::command_from_device::version_reply: { + // TODO + break; + } + default: + break; + } + } + + void update_timecode(int h, int m, int s, int f) + { + for (auto&& m : rcp::timecode(h, m, s, f)) + configuration.midi_out(std::move(m)); + } + + void update_lcd(std::string_view v) + { + auto res = impl.update_lcd(v); + if (!res.empty()) + configuration.midi_out(std::move(res)); + } + + void update_lcd(std::string_view v, int pos) + { + auto res = impl.update_lcd(v, pos); + if (!res.empty()) + configuration.midi_out(std::move(res)); + } + + void command(remote_control_protocol::mixer_command c, bool press) + { + using ce = libremidi::channel_events; + configuration.midi_out(ce::note_on(1, to_underlying(c), press ? 127 : 0)); + configuration.midi_out(ce::note_off(1, to_underlying(c), press ? 127 : 0)); + } + + void control(remote_control_protocol::mixer_control c, int value) + { + using ce = libremidi::channel_events; + configuration.midi_out(ce::control_change(1, to_underlying(c), value)); + } + + void fader(remote_control_protocol::fader c, uint16_t value) + { + int idx = to_underlying(c); + + using ce = libremidi::channel_events; + configuration.midi_out(ce::pitch_bend(idx + 1, value)); + } + + // State machine + enum + { + waiting_for_query, + got_query, + connected, + errored + } current_state{waiting_for_query}; +}; +} diff --git a/include/libremidi/reader.cpp b/include/libremidi/reader.cpp index 5beb5b1..2d2a543 100644 --- a/include/libremidi/reader.cpp +++ b/include/libremidi/reader.cpp @@ -521,7 +521,7 @@ LIBREMIDI_INLINE reader::reader(bool useAbsolute) : ticksPerBeat(480) , startingTempo(120) - , useAbsoluteTicks(useAbsolute) + , m_useAbsoluteTicks(useAbsolute) { } @@ -549,12 +549,12 @@ try return parse_result::invalid; } - const uint8_t* const dataEnd = dataPtr + size; + const uint8_t* const data_end = dataPtr + size; - uint32_t headerId = read_checked::read_uint32_be(dataPtr, dataEnd); - uint32_t headerLength = read_checked::read_uint32_be(dataPtr, dataEnd); + uint32_t header_id = read_checked::read_uint32_be(dataPtr, data_end); + uint32_t header_length = read_checked::read_uint32_be(dataPtr, data_end); - if (static_cast(headerId) != str_to_headerid("MThd") || headerLength != 6) + if (static_cast(header_id) != str_to_headerid("MThd") || header_length != 6) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "couldn't parse header" << std::endl; @@ -563,7 +563,7 @@ try } format = read_checked::read_uint16_be( - dataPtr, dataEnd); //@tofix format type -> save for later eventually + dataPtr, data_end); //@tofix format type -> save for later eventually if (format > 2) { #if defined(__LIBREMIDI_DEBUG__) @@ -571,12 +571,12 @@ try #endif return parse_result::invalid; } - int trackCount = read_checked::read_uint16_be(dataPtr, dataEnd); - uint16_t timeDivision = read_checked::read_uint16_be(dataPtr, dataEnd); + int track_count = read_checked::read_uint16_be(dataPtr, data_end); + uint16_t time_division = read_checked::read_uint16_be(dataPtr, data_end); // CBB: deal with the SMPTE style time coding // timeDivision is described here http://www.sonicspot.com/guide/midifiles.html - if (timeDivision & 0x8000) + if (time_division & 0x8000) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "found SMPTE time frames (unsupported)" << std::endl; @@ -590,18 +590,18 @@ try } startingTempo = 120.0f; // midi default - ticksPerBeat = float(timeDivision); // ticks per beat (a beat is defined as a quarter note) + ticksPerBeat = float(time_division); // ticks per beat (a beat is defined as a quarter note) parse_result result = parse_result::validated; - for (int i = 0; i < trackCount; ++i) + for (int i = 0; i < track_count; ++i) { midi_track track; - headerId = read_checked::read_uint32_be(dataPtr, dataEnd); - headerLength = read_checked::read_uint32_be(dataPtr, dataEnd); + header_id = read_checked::read_uint32_be(dataPtr, data_end); + header_length = read_checked::read_uint32_be(dataPtr, data_end); - if (headerId != str_to_headerid("MTrk")) + if (header_id != str_to_headerid("MTrk")) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "couldn't find track header" << std::endl; @@ -609,8 +609,8 @@ try return parse_result::incomplete; } - int64_t available = dataEnd - dataPtr; - if (available < headerLength) + int64_t available = data_end - dataPtr; + if (available < header_length) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "not enough data available" << std::endl; @@ -618,35 +618,35 @@ try return parse_result::incomplete; } - track.reserve(headerLength / 3); + track.reserve(header_length / 3); - const uint8_t* const trackEnd = dataPtr + headerLength; + const uint8_t* const track_end = dataPtr + header_length; - auto runningEvent = message_type::INVALID; + auto running_event = message_type::INVALID; - std::size_t tickCount = 0; + std::size_t tick_count = 0; - while (dataPtr < trackEnd) + while (dataPtr < track_end) { - const auto tick = read_checked::read_variable_length(dataPtr, trackEnd); - if (useAbsoluteTicks) + const auto tick = read_checked::read_variable_length(dataPtr, track_end); + if (m_useAbsoluteTicks) { - tickCount += tick; + tick_count += tick; } else { - tickCount = tick; + tick_count = tick; } try { track_event ev - = parse_event(static_cast(tickCount), i, dataPtr, trackEnd, runningEvent); + = parse_event(static_cast(tick_count), i, dataPtr, track_end, running_event); if (!ev.m.empty()) { if (!ev.m.is_meta_event()) { - runningEvent = static_cast(ev.m.bytes[0]); + running_event = static_cast(ev.m.bytes[0]); } } else @@ -654,7 +654,7 @@ try #if defined(__LIBREMIDI_DEBUG__) std::cerr << "could not read event" << std::endl; #endif - dataPtr = trackEnd; + dataPtr = track_end; result = parse_result::incomplete; continue; } @@ -666,7 +666,7 @@ try #if defined(__LIBREMIDI_DEBUG__) std::cerr << "" << e.what() << std::endl; #endif - dataPtr = trackEnd; + dataPtr = track_end; result = parse_result::incomplete; continue; } @@ -684,7 +684,7 @@ try if (result == parse_result::validated) { - if (dataPtr != dataEnd) + if (dataPtr != data_end) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl; @@ -706,33 +706,33 @@ catch (const std::exception& e) LIBREMIDI_INLINE double reader::get_end_time() const noexcept { - if (useAbsoluteTicks) + if (m_useAbsoluteTicks) { - double totalLength = 0.; + double total_length = 0.; for (const auto& t : tracks) { if (!t.empty()) { const auto& last_event = t.back(); - if (last_event.tick > totalLength) - totalLength = last_event.tick; + if (last_event.tick > total_length) + total_length = last_event.tick; } } - return totalLength; + return total_length; } else { - double totalLength = 0.; + double total_length = 0.; for (const auto& t : tracks) { - double trackLength = 0.; + double track_length = 0.; for (const auto& e : t) - trackLength += e.tick; + track_length += e.tick; - if (trackLength > totalLength) - totalLength = trackLength; + if (track_length > total_length) + total_length = track_length; } - return totalLength; + return total_length; } } diff --git a/include/libremidi/reader.hpp b/include/libremidi/reader.hpp index 1d99b46..4fda384 100644 --- a/include/libremidi/reader.hpp +++ b/include/libremidi/reader.hpp @@ -66,7 +66,7 @@ public: std::vector tracks; private: - bool useAbsoluteTicks{}; + bool m_useAbsoluteTicks{}; }; } diff --git a/include/libremidi/shared_context.hpp b/include/libremidi/shared_context.hpp index f794aa2..ad9d9a4 100644 --- a/include/libremidi/shared_context.hpp +++ b/include/libremidi/shared_context.hpp @@ -1,8 +1,6 @@ #pragma once #include -#include - -#include +#include namespace libremidi { @@ -24,7 +22,9 @@ public: struct shared_configurations { std::shared_ptr context; - std::any observer, in, out; + observer_api_configuration observer; + input_api_configuration in; + output_api_configuration out; }; LIBREMIDI_EXPORT diff --git a/include/libremidi/ump.hpp b/include/libremidi/ump.hpp index 59d3091..073a272 100644 --- a/include/libremidi/ump.hpp +++ b/include/libremidi/ump.hpp @@ -1,14 +1,27 @@ #pragma once #include -#include - #if LIBREMIDI_NI_MIDI2_COMPAT #include #endif namespace libremidi { +namespace midi2 +{ +// Imported from cmidi2 +enum class message_type +{ + // MIDI 2.0 UMP Section 3. + UTILITY = 0, + SYSTEM = 1, + MIDI_1_CHANNEL = 2, + SYSEX7 = 3, + MIDI_2_CHANNEL = 4, + SYSEX8_MDS = 5, +}; +} + struct ump { alignas(4) uint32_t data[4] = {}; @@ -62,28 +75,16 @@ struct ump constexpr std::size_t size() const noexcept { - // Imported from cmidi2 - enum midi2_message_type + switch (midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF)) { - // MIDI 2.0 UMP Section 3. - UTILITY = 0, - SYSTEM = 1, - MIDI_1_CHANNEL = 2, - SYSEX7 = 3, - MIDI_2_CHANNEL = 4, - SYSEX8_MDS = 5, - }; - - switch (((data[0] & 0xF0000000) >> 28) & 0xF) - { - case UTILITY: - case SYSTEM: - case MIDI_1_CHANNEL: + case midi2::message_type::UTILITY: + case midi2::message_type::SYSTEM: + case midi2::message_type::MIDI_1_CHANNEL: return 1; - case MIDI_2_CHANNEL: - case SYSEX7: + case midi2::message_type::MIDI_2_CHANNEL: + case midi2::message_type::SYSEX7: return 2; - case SYSEX8_MDS: + case midi2::message_type::SYSEX8_MDS: return 4; default: return 0; @@ -109,5 +110,14 @@ struct ump constexpr auto cend() const noexcept { return data + size(); } constexpr auto cbegin() noexcept { return data; } constexpr auto cend() noexcept { return data + size(); } + + constexpr midi2::message_type get_type() const noexcept + { + return midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF); + } + constexpr uint8_t get_group() const noexcept { return (data[0] >> 24) & 0x0F; } + constexpr uint8_t get_channel_0_15() const noexcept { return (data[0] >> 16) & 0x0F; } + constexpr uint8_t get_channel() const noexcept { return get_channel_0_15() + 1; } + constexpr uint8_t get_status_code() const noexcept { return (data[0] >> 16) & 0xF0; } }; } diff --git a/include/libremidi/ump_events.hpp b/include/libremidi/ump_events.hpp new file mode 100644 index 0000000..4d8a52b --- /dev/null +++ b/include/libremidi/ump_events.hpp @@ -0,0 +1,374 @@ +#pragma once +#include +#include +#include + +// FIXME! we need to review them all to check the channels +namespace libremidi::ump_events +{ +inline libremidi::ump note_on(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, pitch, 0, vel, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, pitch, 0, vel, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, pitch, vel); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t group, uint8_t channel, uint8_t index, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc(group, channel, index, value); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump program_change(uint8_t group, uint8_t channel, uint8_t index) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, index, 0, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t group, uint8_t channel, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, value); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t group, uint8_t channel, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, value); + cmidi2_reverse(msg, u.data); + return u; +} +} + +namespace libremidi::from_midi1 +{ +inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, uint8_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on(0, channel, pitch, 0, u7_to_u16(vel), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, uint8_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off(0, channel, pitch, 0, u7_to_u16(vel), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf(0, channel, pitch, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t channel, uint8_t index, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc(0, channel, index, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump program_change(uint8_t channel, uint8_t index) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_program(0, channel, 0, index, 0, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t channel, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf(0, channel, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t channel, uint16_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(0, channel, u16_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} +} + +namespace libremidi::from_01 +{ +inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, double vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on( + 0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits::max(), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, double vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off( + 0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits::max(), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf( + 0, channel, pitch, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t channel, uint8_t index, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc( + 0, channel, index, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t channel, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf( + 0, channel, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t channel, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct( + 0, channel, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} +} + +namespace libremidi::as_midi1 +{ +inline auto note_off(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; + return res; +} + +inline auto note_on(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; + return res; +} + +inline auto poly_pressure(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_paf_note(ump); + res.value = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000; + return res; +} + +inline auto control_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t control; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.control = cmidi2_ump_get_midi2_cc_index(ump); + res.value = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000; + return res; +} + +inline auto program_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t program; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.program = cmidi2_ump_get_midi2_program_program(ump); + return res; +} + +inline auto aftertouch(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000; + return res; +} + +inline auto pitch_bend(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint16_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000; + return res; +} +} + +namespace libremidi::as_01 +{ +inline auto note_off(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity + = double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto note_on(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity + = double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto poly_pressure(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_paf_note(ump); + res.value = double(cmidi2_ump_get_midi2_paf_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto control_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t control; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.control = cmidi2_ump_get_midi2_cc_index(ump); + res.value = double(cmidi2_ump_get_midi2_cc_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto aftertouch(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = double(cmidi2_ump_get_midi2_caf_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto pitch_bend(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value + = double(cmidi2_ump_get_midi2_pitch_bend_data(ump)) / std::numeric_limits::max(); + return res; +} +} diff --git a/include/libremidi/writer.cpp b/include/libremidi/writer.cpp index 984865c..7a078fc 100644 --- a/include/libremidi/writer.cpp +++ b/include/libremidi/writer.cpp @@ -62,14 +62,14 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) } else { - static constexpr auto N = sizeof(value); + static constexpr auto n = sizeof(value); struct storage { - uint8_t bytes[N]; + uint8_t bytes[n]; }; auto data = std::bit_cast(value); - std::reverse(data.bytes, data.bytes + N); - out.write(reinterpret_cast(data.bytes), static_cast(N)); + std::reverse(data.bytes, data.bytes + n); + out.write(reinterpret_cast(data.bytes), static_cast(n)); } return out; } @@ -146,12 +146,12 @@ void writer::write(std::ostream& out) const util::write_be(out, static_cast(tracks.size())); util::write_be(out, ticksPerQuarterNote); - std::vector trackRawData; + std::vector track_raw_data; for (const auto& event_list : tracks) { - trackRawData.clear(); + track_raw_data.clear(); // Rough estimation of the memory to allocate - trackRawData.reserve(event_list.size() * 3); + track_raw_data.reserve(event_list.size() * 3); for (const auto& event : event_list) { @@ -164,7 +164,7 @@ void writer::write(std::ostream& out) const if (msg.get_meta_event_type() == meta_event_type::END_OF_TRACK) continue; - util::write_variable_length(static_cast(event.tick), trackRawData); + util::write_variable_length(static_cast(event.tick), track_raw_data); if ((msg.get_message_type() == message_type::SYSTEM_EXCLUSIVE) || (event.m.get_message_type() == message_type::EOX)) @@ -176,38 +176,38 @@ void writer::write(std::ostream& out) const // In other words, when creating a 0xf0 or 0xf7 MIDI message, // do not insert the VLV byte length yourself, as this code will // do it for you automatically. - trackRawData.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7; + track_raw_data.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7; - util::write_variable_length(static_cast(msg.size()) - 1, trackRawData); + util::write_variable_length(static_cast(msg.size()) - 1, track_raw_data); - trackRawData.insert( - trackRawData.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size()); + track_raw_data.insert( + track_raw_data.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size()); } else { // Non-sysex type of message, so just output the bytes of the message: - trackRawData.insert( - trackRawData.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size()); + track_raw_data.insert( + track_raw_data.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size()); } } - auto size = trackRawData.size(); + auto size = track_raw_data.size(); const auto eot = meta_events::end_of_track(); - if ((size < 3) || !((trackRawData[size - 3] == 0xFF) && (trackRawData[size - 2] == 0x2F))) + if ((size < 3) || !((track_raw_data[size - 3] == 0xFF) && (track_raw_data[size - 2] == 0x2F))) { - trackRawData.emplace_back(0x0); // tick - trackRawData.emplace_back(eot[0]); - trackRawData.emplace_back(eot[1]); - trackRawData.emplace_back(eot[2]); + track_raw_data.emplace_back(0x0); // tick + track_raw_data.emplace_back(eot[0]); + track_raw_data.emplace_back(eot[1]); + track_raw_data.emplace_back(eot[2]); } // Write the track ID marker "MTrk": out.write("MTrk", 4); - util::write_be(out, static_cast(trackRawData.size())); + util::write_be(out, static_cast(track_raw_data.size())); out.write( - reinterpret_cast(trackRawData.data()), - static_cast(trackRawData.size())); + reinterpret_cast(track_raw_data.data()), + static_cast(track_raw_data.size())); } } } diff --git a/include/raygui/dark/README.md b/include/raygui/dark/README.md new file mode 100644 index 0000000..c3595fc --- /dev/null +++ b/include/raygui/dark/README.md @@ -0,0 +1,27 @@ +## style: dark + +Classical dark style with an extra high-def colour touch! Elegant and professional, perfect for the expensive tools! + +![dark style table](style_dark.png) + +## style: provided files + +Several options are provided to add the style to a `raygui` application, choose the one that better fits the project. + +| file name | description | +| :-------- | :---------- | +| `style_dark.rgs` | Binary style file (raygui 4.0), font data compressed (recs, glyphs) | +| `style_dark.txt.rgs` | Text style file, no font data, requires external font provided | +| `style_dark.old.rgs` | Binary style file (raygui 3.x), font data uncompressed (recs, glyphs) | +| `style_dark.h` | Embeddable style as code file, self-contained, includes font data | +| `style_dark.png` | Style table image, contains `rGSf` chunk with binary `rgs` file data | + +## screenshot + +![dark style screen](screenshot.png) + +## about font + +"Pixel Operator" font by de Jayvee Enaguas. + +CC0 1.0 Universal, downloaded from dafont.com: [pixel-operator](https://www.dafont.com/pixel-operator.font) diff --git a/include/raygui/dark/screenshot.old.png b/include/raygui/dark/screenshot.old.png new file mode 100644 index 0000000..712d30c Binary files /dev/null and b/include/raygui/dark/screenshot.old.png differ diff --git a/include/raygui/dark/screenshot.png b/include/raygui/dark/screenshot.png index 712d30c..cc9dbee 100644 Binary files a/include/raygui/dark/screenshot.png and b/include/raygui/dark/screenshot.png differ diff --git a/include/raygui/dark/style_dark.h b/include/raygui/dark/style_dark.h index 7986ca8..0e8d44f 100644 --- a/include/raygui/dark/style_dark.h +++ b/include/raygui/dark/style_dark.h @@ -7,7 +7,7 @@ // more info and bugs-report: github.com/raysan5/raygui // // feedback and support: ray[at]raylibtech.com // // // -// Copyright (c) 2020-2024 raylib technologies (@raylibtech) // +// Copyright (c) 2020-2025 raylib technologies (@raylibtech) // // // ////////////////////////////////////////////////////////////////////////////////// @@ -15,144 +15,144 @@ // Custom style name: Dark static const GuiStyleProp darkStyleProps[DARK_STYLE_PROPS_COUNT] = { - { 0, 0, (long int)0x878787ff }, // DEFAULT_BORDER_COLOR_NORMAL - { 0, 1, (long int)0x2c2c2cff }, // DEFAULT_BASE_COLOR_NORMAL - { 0, 2, (long int)0xc3c3c3ff }, // DEFAULT_TEXT_COLOR_NORMAL - { 0, 3, (long int)0xe1e1e1ff }, // DEFAULT_BORDER_COLOR_FOCUSED - { 0, 4, (long int)0x848484ff }, // DEFAULT_BASE_COLOR_FOCUSED - { 0, 5, (long int)0x181818ff }, // DEFAULT_TEXT_COLOR_FOCUSED - { 0, 6, (long int)0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED - { 0, 7, (long int)0xefefefff }, // DEFAULT_BASE_COLOR_PRESSED - { 0, 8, (long int)0x202020ff }, // DEFAULT_TEXT_COLOR_PRESSED - { 0, 9, (long int)0x6a6a6aff }, // DEFAULT_BORDER_COLOR_DISABLED - { 0, 10, (long int)0x818181ff }, // DEFAULT_BASE_COLOR_DISABLED - { 0, 11, (long int)0x606060ff }, // DEFAULT_TEXT_COLOR_DISABLED - { 0, 16, (long int)0x00000010 }, // DEFAULT_TEXT_SIZE - { 0, 17, (long int)0x00000000 }, // DEFAULT_TEXT_SPACING - { 0, 18, (long int)0x9d9d9dff }, // DEFAULT_LINE_COLOR - { 0, 19, (long int)0x3c3c3cff }, // DEFAULT_BACKGROUND_COLOR - { 0, 20, (long int)0x00000018 }, // DEFAULT_TEXT_LINE_SPACING - { 1, 5, (long int)0xf7f7f7ff }, // LABEL_TEXT_COLOR_FOCUSED - { 1, 8, (long int)0x898989ff }, // LABEL_TEXT_COLOR_PRESSED - { 4, 5, (long int)0xb0b0b0ff }, // SLIDER_TEXT_COLOR_FOCUSED - { 5, 5, (long int)0x848484ff }, // PROGRESSBAR_TEXT_COLOR_FOCUSED - { 9, 5, (long int)0xf5f5f5ff }, // TEXTBOX_TEXT_COLOR_FOCUSED - { 10, 5, (long int)0xf6f6f6ff }, // VALUEBOX_TEXT_COLOR_FOCUSED + { 0, 0, (int)0x878787ff }, // DEFAULT_BORDER_COLOR_NORMAL + { 0, 1, (int)0x2c2c2cff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 2, (int)0xc3c3c3ff }, // DEFAULT_TEXT_COLOR_NORMAL + { 0, 3, (int)0xe1e1e1ff }, // DEFAULT_BORDER_COLOR_FOCUSED + { 0, 4, (int)0x848484ff }, // DEFAULT_BASE_COLOR_FOCUSED + { 0, 5, (int)0x181818ff }, // DEFAULT_TEXT_COLOR_FOCUSED + { 0, 6, (int)0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED + { 0, 7, (int)0xefefefff }, // DEFAULT_BASE_COLOR_PRESSED + { 0, 8, (int)0x202020ff }, // DEFAULT_TEXT_COLOR_PRESSED + { 0, 9, (int)0x6a6a6aff }, // DEFAULT_BORDER_COLOR_DISABLED + { 0, 10, (int)0x818181ff }, // DEFAULT_BASE_COLOR_DISABLED + { 0, 11, (int)0x606060ff }, // DEFAULT_TEXT_COLOR_DISABLED + { 0, 16, (int)0x00000010 }, // DEFAULT_TEXT_SIZE + { 0, 17, (int)0x00000000 }, // DEFAULT_TEXT_SPACING + { 0, 18, (int)0x9d9d9dff }, // DEFAULT_LINE_COLOR + { 0, 19, (int)0x3c3c3cff }, // DEFAULT_BACKGROUND_COLOR + { 0, 20, (int)0x00000018 }, // DEFAULT_TEXT_LINE_SPACING + { 1, 5, (int)0xf7f7f7ff }, // LABEL_TEXT_COLOR_FOCUSED + { 1, 8, (int)0x898989ff }, // LABEL_TEXT_COLOR_PRESSED + { 4, 5, (int)0xb0b0b0ff }, // SLIDER_TEXT_COLOR_FOCUSED + { 5, 5, (int)0x848484ff }, // PROGRESSBAR_TEXT_COLOR_FOCUSED + { 9, 5, (int)0xf5f5f5ff }, // TEXTBOX_TEXT_COLOR_FOCUSED + { 10, 5, (int)0xf6f6f6ff }, // VALUEBOX_TEXT_COLOR_FOCUSED }; // WARNING: This style uses a custom font: "PixelOperator.ttf" (size: 16, spacing: 0) -#define DARK_STYLE_FONT_ATLAS_COMP_SIZE 2126 +#define DARK_STYLE_FONT_ATLAS_COMP_SIZE 2138 // Font atlas image pixels data: DEFLATE compressed static unsigned char darkFontData[DARK_STYLE_FONT_ATLAS_COMP_SIZE] = { 0xed, - 0xdd, 0xdb, 0x72, 0xa4, 0x3a, 0x12, 0x05, 0x50, 0xfd, 0xff, 0x4f, 0xe7, 0x3c, 0x4c, 0x4c, 0x4c, 0x74, 0x9c, 0xd3, 0x20, - 0xa5, 0x52, 0x17, 0xec, 0xd5, 0xeb, 0xcd, 0xe5, 0x76, 0x51, 0xa0, 0x94, 0x84, 0x28, 0x36, 0xd1, 0x00, 0x00, 0x00, 0x80, - 0x5f, 0x2f, 0xfe, 0xf5, 0x27, 0xf1, 0xd7, 0xdf, 0x8c, 0xee, 0xbf, 0xf3, 0xfc, 0xf3, 0xff, 0xbd, 0x1a, 0x0f, 0xef, 0xd5, - 0xb7, 0xad, 0xa3, 0xef, 0x1b, 0x03, 0x7b, 0xe2, 0xdf, 0xb7, 0x2f, 0xba, 0xff, 0xee, 0xdf, 0x3e, 0xdf, 0xf8, 0xef, 0x3f, - 0xfd, 0xa5, 0xe7, 0xad, 0x8f, 0xa1, 0xfd, 0x3e, 0xfe, 0x7f, 0xc6, 0x8e, 0x62, 0x2c, 0xd9, 0xf7, 0xef, 0x5b, 0x37, 0xbe, - 0xed, 0x95, 0xff, 0x27, 0x1e, 0x3e, 0x4f, 0xe6, 0x28, 0xf5, 0xd4, 0xd6, 0x0d, 0xf5, 0x1f, 0x5d, 0x95, 0x18, 0xa5, 0x2d, - 0xe7, 0xff, 0xef, 0x1a, 0x85, 0x3d, 0x55, 0x76, 0x4f, 0x3e, 0x55, 0xfa, 0xf8, 0xfe, 0x78, 0x6a, 0xfd, 0xe3, 0x3d, 0x60, - 0x94, 0x7c, 0x96, 0xf9, 0xd6, 0x18, 0xd3, 0xbf, 0xdd, 0xd7, 0x92, 0x6a, 0x3e, 0xd5, 0xcc, 0x2b, 0xad, 0xe4, 0x28, 0x9d, - 0xa8, 0xff, 0x48, 0xf7, 0x54, 0x31, 0x58, 0xd1, 0x15, 0x7b, 0x30, 0xd3, 0x0f, 0xb7, 0x8d, 0xf5, 0x1f, 0xa5, 0x73, 0x97, - 0x28, 0x6b, 0x73, 0x27, 0xea, 0xff, 0x79, 0xd4, 0x6e, 0x25, 0xed, 0x26, 0x5e, 0xf6, 0x51, 0x6d, 0x2d, 0xaf, 0x9d, 0xa7, - 0xae, 0xae, 0xff, 0xe7, 0xdf, 0x1c, 0x1d, 0x6f, 0x63, 0x70, 0xec, 0xae, 0xd8, 0x4f, 0xb9, 0xf1, 0xbf, 0x7e, 0x3f, 0xc6, - 0x5f, 0xc7, 0xe6, 0xec, 0x3c, 0x64, 0x64, 0x0f, 0xc7, 0xe0, 0xd9, 0xd5, 0xec, 0x08, 0x7b, 0x72, 0xfc, 0xef, 0x9b, 0x0b, - 0xaa, 0xff, 0xb5, 0xf5, 0x9f, 0xf9, 0x24, 0xd1, 0xb9, 0x05, 0x99, 0x31, 0xb1, 0x5d, 0x50, 0xff, 0xa3, 0xfd, 0xd0, 0xf3, - 0xbe, 0xaa, 0x9a, 0xcb, 0x8f, 0xae, 0x4b, 0xf4, 0x9d, 0x61, 0xef, 0x6b, 0x85, 0xe3, 0x73, 0xa3, 0xf1, 0xfa, 0x1f, 0xef, - 0x3d, 0xdf, 0xfe, 0xda, 0xe8, 0x5e, 0xfd, 0xfb, 0x99, 0xdd, 0xfc, 0x1e, 0xdf, 0x57, 0xff, 0x91, 0x18, 0x53, 0xce, 0xd7, - 0x73, 0x4d, 0xfd, 0x47, 0x62, 0xe5, 0xa2, 0x95, 0xad, 0xf2, 0xe5, 0xd6, 0x25, 0xea, 0xd6, 0x0c, 0xce, 0x1f, 0x81, 0xf7, - 0xfa, 0x7f, 0x3b, 0x1f, 0x3c, 0x3f, 0xfe, 0xc7, 0x15, 0xe3, 0x7f, 0xa4, 0xd7, 0x86, 0x5b, 0xa2, 0xf7, 0xfd, 0x5a, 0xfd, - 0x47, 0xa2, 0x66, 0xa2, 0xe4, 0xbc, 0xbc, 0x2d, 0x59, 0xcb, 0xab, 0x5d, 0x33, 0xdc, 0x73, 0xb4, 0xde, 0x46, 0xd1, 0xaa, - 0xb3, 0x9a, 0x9f, 0x55, 0xff, 0xad, 0x60, 0xfc, 0xef, 0x9b, 0x03, 0xcc, 0x7e, 0x8e, 0xf8, 0xd8, 0xe8, 0x5f, 0x59, 0xff, - 0xbb, 0x56, 0xec, 0xd7, 0xb7, 0xc6, 0xb8, 0xe2, 0xc8, 0x58, 0xff, 0xaf, 0x9c, 0xff, 0xe7, 0xd7, 0x00, 0xc6, 0xce, 0x63, - 0xe2, 0xd2, 0xea, 0xcf, 0x9c, 0x33, 0x57, 0x5d, 0xb1, 0xaf, 0xfd, 0x36, 0x41, 0x24, 0xbe, 0xab, 0x51, 0xb3, 0x8a, 0xb3, - 0xfa, 0x18, 0x8c, 0x8f, 0xff, 0xbb, 0xae, 0xff, 0xbf, 0xcf, 0xb9, 0xef, 0x18, 0xff, 0xdb, 0xeb, 0xe8, 0x7e, 0xcb, 0xf6, - 0x00, 0x7b, 0x7b, 0x80, 0x50, 0xfd, 0xe0, 0x7b, 0xc4, 0x80, 0xfa, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0a, 0xbf, 0x9f, 0x5f, 0x9f, 0x63, 0x30, 0x9e, 0x84, 0xbd, 0x3e, 0x81, 0x7d, 0x26, 0x13, 0x38, 0xbb, - 0x87, 0xaa, 0xfe, 0xe2, 0x73, 0x76, 0xfc, 0x48, 0xd2, 0x42, 0x7f, 0xcb, 0x88, 0xe1, 0xa7, 0x1f, 0x54, 0x26, 0x0a, 0x64, - 0x32, 0x0d, 0xda, 0x60, 0x9b, 0xa8, 0xcd, 0x52, 0x1a, 0xdb, 0xfa, 0x3d, 0x2d, 0xfe, 0x0b, 0xf5, 0x7f, 0xfe, 0x95, 0x96, - 0xc8, 0xef, 0xd9, 0x59, 0xff, 0xa3, 0x6d, 0x2f, 0x97, 0x8f, 0x3b, 0x37, 0x96, 0xec, 0x4e, 0x14, 0x8b, 0xc2, 0x63, 0xb8, - 0x6f, 0x5c, 0xfd, 0x33, 0x43, 0x67, 0x47, 0xbb, 0x9e, 0xa9, 0xff, 0xd1, 0xec, 0xd3, 0x78, 0xe8, 0x85, 0xef, 0xa9, 0xff, - 0xb6, 0xa5, 0xfe, 0xe3, 0x23, 0xf5, 0x5f, 0x9d, 0x24, 0x53, 0x95, 0x42, 0x9e, 0x49, 0xbe, 0x19, 0x3f, 0x86, 0xf9, 0x84, - 0xdf, 0x6c, 0x8b, 0xfb, 0x4e, 0xfd, 0x67, 0xda, 0xd6, 0x73, 0xce, 0xaa, 0xfa, 0x7f, 0xdf, 0xb7, 0x99, 0xd1, 0x64, 0xfe, - 0xe7, 0x35, 0x95, 0xbf, 0x27, 0x47, 0xf7, 0xed, 0x39, 0x5a, 0x6b, 0xdb, 0xca, 0xec, 0x58, 0xf9, 0x95, 0xfa, 0x8f, 0xe4, - 0x5f, 0x89, 0x97, 0x1e, 0xa5, 0x36, 0x81, 0xbd, 0x26, 0xb5, 0x6d, 0x6f, 0xfd, 0x57, 0x3f, 0xf7, 0xe7, 0x8e, 0xfa, 0x3f, - 0x9f, 0xa3, 0xbb, 0xea, 0x1c, 0xae, 0x76, 0xac, 0xfc, 0xd9, 0xe3, 0xff, 0x7b, 0x2f, 0xb9, 0xa3, 0x7d, 0xcc, 0x8c, 0xf4, - 0x27, 0xc7, 0xff, 0x73, 0x3f, 0x3f, 0x5b, 0xff, 0x55, 0xad, 0x3d, 0x36, 0xce, 0x4c, 0xb2, 0x63, 0xe5, 0xce, 0x55, 0xb2, - 0xdd, 0xe7, 0xff, 0xab, 0x3e, 0x59, 0x1b, 0x9e, 0x4d, 0xc4, 0xb5, 0xf5, 0x3f, 0xb3, 0x0a, 0xb9, 0x6e, 0xbe, 0x70, 0x66, - 0xfe, 0x9f, 0x7b, 0xbe, 0x6d, 0xe6, 0xa9, 0x80, 0xb7, 0x8d, 0xff, 0xbb, 0xae, 0x92, 0xb5, 0xcd, 0x35, 0xb9, 0xf7, 0xbd, - 0x2a, 0x9f, 0x52, 0x37, 0x9e, 0xdf, 0x1f, 0xc5, 0x33, 0xbc, 0xaf, 0xd7, 0x7f, 0x7e, 0x95, 0xbf, 0x15, 0xad, 0x4a, 0x9e, - 0x9b, 0xff, 0xef, 0x7e, 0xd2, 0x49, 0xe6, 0x5d, 0x6a, 0xfa, 0xdf, 0xbe, 0xab, 0xc2, 0xb5, 0xa3, 0xc0, 0xdf, 0xaf, 0x36, - 0x44, 0xd1, 0xc8, 0x51, 0x95, 0xdf, 0xff, 0xe7, 0xb6, 0x8d, 0x3f, 0xf1, 0x6c, 0xfc, 0x4a, 0x7c, 0x0c, 0x3e, 0x4f, 0xff, - 0x44, 0xfd, 0x67, 0xde, 0x39, 0xf3, 0xbf, 0x46, 0x8f, 0x61, 0x65, 0xfd, 0x9f, 0xeb, 0x01, 0xe4, 0x6e, 0xc2, 0x8d, 0xb3, - 0x18, 0xe0, 0xe7, 0xf4, 0x00, 0x9e, 0x70, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xec, 0xbe, 0x13, 0xfb, 0xef, 0xd9, 0x5c, 0x99, 0xd4, 0xf8, 0xf1, 0xfb, 0xa4, 0x77, 0x26, 0xe6, 0xb7, 0x97, 0x14, 0xb2, - 0xe7, 0xdf, 0xfa, 0xf3, 0x95, 0x8a, 0x3c, 0xde, 0x28, 0xbc, 0x6b, 0xfd, 0x29, 0x93, 0xe4, 0x39, 0x4b, 0x71, 0x34, 0x77, - 0xe0, 0x7d, 0x0f, 0x8f, 0x1f, 0x97, 0x99, 0xd7, 0x5a, 0x61, 0x9a, 0x42, 0x24, 0xd2, 0x5e, 0xde, 0xf6, 0x7a, 0x7f, 0x36, - 0x70, 0x94, 0x6d, 0xf1, 0x58, 0x1d, 0x55, 0x65, 0x27, 0xdf, 0x9e, 0x98, 0x3f, 0x9f, 0xbc, 0x13, 0x2f, 0xad, 0xae, 0xbf, - 0xfe, 0x7b, 0x92, 0x23, 0xd6, 0xe7, 0x18, 0xf5, 0xfc, 0x34, 0x93, 0x7a, 0x31, 0x96, 0xc1, 0x36, 0xd7, 0x06, 0x56, 0x8f, - 0x50, 0x6d, 0xb2, 0x42, 0x62, 0xaa, 0xa5, 0xcc, 0x67, 0xff, 0xbd, 0x6f, 0x69, 0x1b, 0x1c, 0xb9, 0x6e, 0x48, 0xcc, 0x8c, - 0xce, 0xfa, 0xaa, 0xfe, 0x69, 0x1b, 0xec, 0x6f, 0x32, 0x79, 0x75, 0x51, 0x9a, 0x44, 0x91, 0xff, 0x3f, 0x91, 0x48, 0x6d, - 0x6e, 0xc9, 0x2c, 0xbe, 0x7c, 0x46, 0x7c, 0x26, 0xa7, 0x2a, 0x16, 0xef, 0xd9, 0x4c, 0xfd, 0xb7, 0x43, 0xf5, 0x9f, 0x49, - 0x3d, 0x38, 0x9f, 0x98, 0x1d, 0xa9, 0xda, 0xdd, 0x5b, 0xff, 0xd1, 0xd1, 0x7f, 0x65, 0x66, 0xd7, 0x51, 0x78, 0xce, 0xf0, - 0x3e, 0xe3, 0xad, 0x1e, 0xff, 0xdb, 0xe3, 0x7e, 0x1a, 0x4f, 0x75, 0x5b, 0x3f, 0x42, 0x65, 0x7a, 0x93, 0xb1, 0xfa, 0x5f, - 0xdd, 0x63, 0xcd, 0x3c, 0x07, 0x29, 0x3e, 0x37, 0xfe, 0x9f, 0x49, 0xcc, 0x8e, 0xc4, 0xd9, 0x75, 0xe6, 0x7d, 0xeb, 0xd7, - 0x5e, 0xa2, 0xb0, 0xfe, 0x33, 0x3d, 0x40, 0xbe, 0xfe, 0x77, 0xce, 0xa6, 0xb3, 0x79, 0x93, 0x31, 0xdd, 0x86, 0xce, 0xd4, - 0x7f, 0xe5, 0xd3, 0x96, 0x56, 0x24, 0xe6, 0x8e, 0x3f, 0xd9, 0xf1, 0xd4, 0xf8, 0xdf, 0x52, 0xe3, 0x7f, 0x6e, 0x9c, 0xa8, - 0x3b, 0xf6, 0xb5, 0x73, 0xe5, 0xf7, 0xfa, 0x8f, 0x0d, 0xef, 0xb6, 0xaf, 0xfe, 0x33, 0x3d, 0x61, 0x2c, 0x9b, 0xff, 0x67, - 0xab, 0x25, 0x86, 0xe7, 0xd7, 0x3b, 0x13, 0x73, 0x33, 0xeb, 0xcc, 0xf7, 0xd7, 0x7f, 0xe5, 0x33, 0x4c, 0xda, 0x54, 0xfa, - 0x72, 0x94, 0x8e, 0xc8, 0xfb, 0xc6, 0xff, 0x15, 0xb3, 0xe9, 0x4c, 0xfa, 0xfe, 0x3d, 0xeb, 0x7f, 0xad, 0xf8, 0x88, 0xb5, - 0xe3, 0x79, 0x88, 0x2b, 0x66, 0x85, 0x27, 0xce, 0xff, 0xab, 0x12, 0xd8, 0x73, 0xe7, 0xff, 0xd5, 0x2b, 0xf2, 0x73, 0xc7, - 0xf2, 0xde, 0xf1, 0x3f, 0xd7, 0x42, 0x62, 0x68, 0x7c, 0x6e, 0x9f, 0xaa, 0xff, 0x5b, 0xbe, 0xd1, 0x50, 0xd1, 0x6f, 0x45, - 0x49, 0xef, 0x96, 0x79, 0x6e, 0x4b, 0xe5, 0x3c, 0xaa, 0x72, 0xfd, 0x6a, 0xe6, 0xfa, 0xff, 0xcc, 0xb7, 0x53, 0x56, 0xbf, - 0x5b, 0xfe, 0xfa, 0xff, 0xda, 0xfa, 0x8f, 0xe2, 0x2d, 0xe6, 0x9e, 0x5e, 0xea, 0xe6, 0xf7, 0xd3, 0x7e, 0xee, 0x38, 0x82, - 0x6a, 0x59, 0xeb, 0x39, 0x71, 0x3e, 0xc4, 0xef, 0x9a, 0x33, 0xf3, 0xd3, 0xbf, 0xe1, 0x6d, 0x3f, 0x18, 0x9b, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xef, 0xde, 0xc3, 0x14, 0xaf, 0x77, 0x38, 0xbc, 0x65, 0x4b, - 0x64, 0x72, 0x29, 0x5b, 0x69, 0xce, 0x5d, 0x4b, 0x24, 0xf3, 0x67, 0xb7, 0xae, 0x77, 0xaf, 0xac, 0xff, 0x64, 0xb3, 0x69, - 0xe6, 0x99, 0xc4, 0xf9, 0xcc, 0x3e, 0x3e, 0x7b, 0x34, 0xdf, 0x32, 0x3c, 0x22, 0x91, 0xc6, 0x5a, 0x79, 0xbc, 0x5a, 0x6a, - 0xdf, 0x65, 0x5b, 0x59, 0x6f, 0x0f, 0xd0, 0x9f, 0x0f, 0x53, 0x7f, 0x8f, 0x69, 0xa4, 0xf6, 0x79, 0x4d, 0x32, 0xff, 0x4c, - 0x5b, 0x6a, 0xe9, 0x04, 0xe2, 0x6c, 0xab, 0xe9, 0xfd, 0x5b, 0x31, 0xfd, 0x97, 0xdf, 0xb3, 0x5a, 0xe3, 0x73, 0x47, 0x33, - 0x9b, 0x17, 0x5f, 0x7d, 0xbc, 0x6a, 0xd3, 0xdb, 0x6a, 0x7a, 0x80, 0x8a, 0xfa, 0xaf, 0xdb, 0xca, 0xfd, 0xf7, 0xc6, 0x46, - 0xf2, 0x7e, 0xfa, 0xf8, 0xfc, 0xfd, 0xa7, 0xf1, 0xd1, 0x24, 0x81, 0x5c, 0xca, 0xe4, 0xae, 0x4f, 0x95, 0xcb, 0xe5, 0xca, - 0xa7, 0xb7, 0xed, 0x1c, 0xff, 0x33, 0x49, 0x52, 0x99, 0xb4, 0xd0, 0xea, 0x57, 0xb2, 0xbd, 0x65, 0xae, 0xfe, 0x33, 0x99, - 0x4d, 0x51, 0x94, 0x67, 0x5c, 0xf7, 0xdc, 0x93, 0x48, 0xce, 0xaf, 0x4f, 0x1e, 0xcd, 0x78, 0x4d, 0x25, 0xaf, 0xca, 0xd8, - 0xdd, 0xfb, 0x4a, 0x4c, 0x26, 0x3e, 0x44, 0xd7, 0xf1, 0x8e, 0xe9, 0xb1, 0x70, 0xd7, 0xd3, 0x02, 0x33, 0xcf, 0xa4, 0x68, - 0xc9, 0xd4, 0xcc, 0x28, 0x9b, 0xe1, 0x57, 0xbf, 0xb2, 0xa2, 0xfe, 0xe3, 0xe1, 0x3c, 0xb9, 0xfa, 0x99, 0x65, 0xd5, 0x47, - 0xb3, 0xa7, 0x67, 0xf8, 0x62, 0xfd, 0xaf, 0x9d, 0xa7, 0xd5, 0x9c, 0xff, 0x47, 0xfa, 0x59, 0x3c, 0x77, 0xb6, 0x98, 0xe8, - 0x4c, 0x77, 0xfe, 0x69, 0xf5, 0xbf, 0x77, 0x1f, 0x57, 0xf7, 0xe6, 0xcf, 0x33, 0xec, 0xf6, 0xd1, 0xfa, 0x9f, 0x49, 0x8b, - 0xaf, 0x39, 0x5f, 0x79, 0x5f, 0xff, 0xaf, 0x5c, 0xd9, 0xb8, 0xa3, 0xfe, 0x6f, 0xa9, 0xf2, 0xb1, 0x7d, 0xbe, 0xb6, 0xfe, - 0xcf, 0xd7, 0xd0, 0x6c, 0xf2, 0xfb, 0xf9, 0x6d, 0x8f, 0xb2, 0xf3, 0xff, 0x9a, 0x75, 0xa1, 0xea, 0x79, 0xc4, 0x6d, 0x3d, - 0xe8, 0xaa, 0xa7, 0xab, 0xbd, 0xcd, 0x1d, 0xaa, 0xcf, 0x77, 0xef, 0x18, 0xff, 0x43, 0xfd, 0x2f, 0x58, 0x0b, 0xaf, 0x1d, - 0x25, 0xf3, 0x2b, 0xcc, 0x33, 0xfd, 0xc9, 0xf8, 0xb3, 0xec, 0x6b, 0x57, 0xc9, 0x32, 0x7d, 0x68, 0xcf, 0x2b, 0xf9, 0x27, - 0x6e, 0x7c, 0x75, 0xfe, 0xdf, 0xf3, 0x54, 0xa0, 0x6f, 0xd5, 0x7f, 0x24, 0xd7, 0xb7, 0xee, 0xa8, 0xff, 0xb5, 0x57, 0x06, - 0xd6, 0x5f, 0xcb, 0xbf, 0x79, 0xfe, 0xbf, 0xa2, 0xcf, 0xfb, 0x76, 0xfd, 0x7f, 0x61, 0x0e, 0xbd, 0xe6, 0x5b, 0x28, 0xe7, - 0xea, 0x7f, 0x6e, 0xdc, 0x56, 0xff, 0x2b, 0x56, 0x8c, 0xf3, 0x9f, 0x7a, 0xc5, 0xf9, 0x7f, 0xef, 0x4c, 0xe3, 0xe7, 0xd7, - 0x7f, 0x7d, 0xd2, 0xf9, 0x9a, 0xf3, 0xcd, 0x76, 0xe9, 0x77, 0x61, 0xee, 0xfa, 0x4e, 0xf2, 0x9e, 0x16, 0xb3, 0x66, 0xed, - 0x23, 0xf3, 0x94, 0xde, 0xda, 0xa7, 0x39, 0xc5, 0xa2, 0x4a, 0xcf, 0x5d, 0xe5, 0xdd, 0x73, 0xfd, 0x7f, 0xef, 0x37, 0x8a, - 0xaa, 0xb7, 0x50, 0xfd, 0x7f, 0xa3, 0x0f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xdb, - 0xee, 0xd0, 0xdb, 0x9b, 0xa8, 0x5f, 0x9f, 0x82, 0x71, 0x7b, 0xa2, 0xfe, 0xdb, 0x5d, 0x33, 0xed, 0xda, 0x44, 0xfd, 0xd6, - 0x95, 0x4c, 0xf4, 0xbd, 0x44, 0xfd, 0xdf, 0x78, 0xc7, 0xc7, 0x6d, 0x89, 0xfa, 0x75, 0x77, 0x4e, 0xdf, 0x9e, 0xa8, 0xdf, - 0x5e, 0xf2, 0x87, 0x6e, 0x4e, 0xd4, 0xcf, 0xdf, 0x3b, 0x75, 0x6b, 0xa2, 0x3e, 0x6b, 0xef, 0x98, 0xec, 0xed, 0xfb, 0xe3, - 0xe0, 0x31, 0x5b, 0x75, 0x77, 0x69, 0x7c, 0xf4, 0xce, 0xcf, 0xf1, 0x8c, 0xa6, 0x15, 0x3d, 0x4a, 0xe5, 0x5d, 0x9a, 0xeb, - 0x13, 0xf5, 0x7f, 0x4a, 0xbd, 0xee, 0x4a, 0xd4, 0x7f, 0xcf, 0x19, 0x8b, 0x4d, 0xdb, 0x1f, 0x43, 0xf3, 0xc6, 0xb5, 0x89, - 0x5a, 0xcf, 0xfd, 0xe8, 0xbd, 0xcf, 0x47, 0xf8, 0x76, 0xa2, 0xf6, 0x6f, 0xab, 0xff, 0x55, 0xfb, 0x29, 0x8a, 0xaa, 0xae, - 0xf7, 0x95, 0x7d, 0x59, 0x41, 0x75, 0xf5, 0x1f, 0xe5, 0xaf, 0xed, 0x4d, 0xd4, 0xca, 0xae, 0x27, 0xa8, 0xff, 0xef, 0xac, - 0x01, 0xe4, 0xd6, 0xad, 0xe6, 0x12, 0x75, 0x4f, 0xb6, 0x80, 0x9d, 0x89, 0x5a, 0x5f, 0xaf, 0xff, 0xcc, 0xac, 0x5c, 0xfd, - 0xdf, 0x39, 0xff, 0x8f, 0xb2, 0x57, 0x32, 0xbd, 0x46, 0x7e, 0xfd, 0xff, 0xcb, 0x4f, 0xd4, 0xfa, 0x99, 0xf5, 0x7f, 0x47, - 0xa2, 0xae, 0xfa, 0x3f, 0x33, 0xff, 0x5f, 0xf7, 0x54, 0x82, 0x9d, 0x89, 0xba, 0xc6, 0xff, 0x55, 0x15, 0x71, 0x77, 0x95, - 0xdf, 0x95, 0x9b, 0xfb, 0xcd, 0xf3, 0xff, 0xdc, 0x93, 0x40, 0xb3, 0x3d, 0xc0, 0xbe, 0x84, 0xd6, 0x13, 0x89, 0xfa, 0x12, - 0x75, 0x67, 0x8e, 0xd7, 0xf3, 0xec, 0xef, 0x9b, 0xd7, 0x63, 0x6e, 0x9f, 0xff, 0xd7, 0x5e, 0xab, 0xf9, 0xe7, 0x9a, 0xd2, - 0xea, 0x15, 0xef, 0x55, 0xf3, 0xff, 0xda, 0x2b, 0x5b, 0x12, 0x75, 0xab, 0x8f, 0xda, 0x6f, 0x5f, 0xff, 0x3b, 0xdb, 0xd3, - 0xc0, 0xee, 0x79, 0xed, 0xf9, 0x9e, 0x10, 0xf8, 0xda, 0x37, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xdd, 0xdb, 0x92, 0xa5, 0xba, 0x0d, 0x00, 0x50, 0xff, 0xff, 0x4f, 0x2b, 0x0f, 0xa9, 0x54, 0x32, 0x95, 0xd3, 0x80, 0x64, + 0xd9, 0x98, 0x9e, 0x35, 0xeb, 0xad, 0x77, 0x4f, 0xc3, 0x36, 0x96, 0x6f, 0x80, 0x1c, 0x03, 0x00, 0x00, 0x00, 0xf8, 0xeb, + 0xc5, 0x3f, 0xfe, 0x24, 0x7e, 0xfc, 0xcd, 0x78, 0xfc, 0x77, 0xae, 0x7f, 0xfe, 0x9f, 0x4f, 0xe3, 0xe2, 0x58, 0xcf, 0xce, + 0x35, 0x7b, 0xdc, 0x48, 0x94, 0xc4, 0x3f, 0x9f, 0x5f, 0x3c, 0xfe, 0xbb, 0x3f, 0x7d, 0xbf, 0xfc, 0xef, 0x5f, 0xfd, 0xa5, + 0xeb, 0xb3, 0x8f, 0x54, 0xb9, 0xe7, 0xff, 0x4f, 0xee, 0x2a, 0xc6, 0x92, 0xb2, 0xbf, 0x3f, 0xbb, 0xfc, 0xb9, 0x77, 0xfe, + 0x9f, 0xb8, 0xf8, 0x3e, 0x95, 0xab, 0xf4, 0x24, 0xb6, 0x4e, 0x88, 0xff, 0x78, 0x14, 0x89, 0xd1, 0x5a, 0x73, 0xfe, 0x7b, + 0xd4, 0x68, 0x6c, 0xa9, 0xaa, 0x25, 0x79, 0x15, 0xe9, 0xf9, 0xf2, 0xb8, 0xaa, 0xfd, 0xf9, 0x16, 0x30, 0x5a, 0xbe, 0xcb, + 0x7c, 0x6d, 0x8c, 0xe9, 0xdf, 0x7e, 0x56, 0x93, 0x7a, 0xbe, 0xd5, 0xcc, 0x27, 0xa3, 0xe5, 0x2a, 0xbd, 0x11, 0xff, 0x51, + 0x6e, 0xa9, 0x22, 0x19, 0xd1, 0x1d, 0x25, 0x58, 0x69, 0x87, 0xc7, 0xc6, 0xf8, 0x8f, 0xd6, 0xb1, 0x4b, 0xb4, 0xd5, 0xb9, + 0x37, 0xe2, 0xff, 0xba, 0xd7, 0x1e, 0x2d, 0xf5, 0x26, 0x6e, 0xca, 0xa8, 0x37, 0x96, 0xd7, 0x8e, 0x53, 0x57, 0xc7, 0xff, + 0xf5, 0x6f, 0x66, 0xfb, 0xdb, 0x48, 0xf6, 0xdd, 0x1d, 0xe5, 0x54, 0xeb, 0xff, 0xfb, 0xcb, 0x31, 0x7e, 0xec, 0x9b, 0xab, + 0xe3, 0x90, 0x4c, 0x09, 0x47, 0x72, 0x76, 0x35, 0xdb, 0xc3, 0xbe, 0xd9, 0xff, 0x3f, 0x1b, 0x0b, 0x8a, 0xff, 0xb5, 0xf1, + 0x5f, 0xf9, 0x26, 0xf1, 0xf0, 0x0c, 0x2a, 0x7d, 0xe2, 0x38, 0x20, 0xfe, 0xb3, 0xed, 0xd0, 0x75, 0x59, 0x75, 0x8d, 0xe5, + 0xb3, 0xeb, 0x12, 0xcf, 0x66, 0xd8, 0xfb, 0x6a, 0x61, 0x7e, 0x6c, 0x94, 0x8f, 0xff, 0x7c, 0xeb, 0x79, 0xf7, 0xd7, 0xb2, + 0xa5, 0xfa, 0xf3, 0xcc, 0x6e, 0xbe, 0xc4, 0xf7, 0xc5, 0x7f, 0x14, 0xfa, 0x94, 0xf7, 0xe3, 0xb9, 0x27, 0xfe, 0xa3, 0xb0, + 0x72, 0x31, 0xda, 0x56, 0xf9, 0x6a, 0xeb, 0x12, 0x7d, 0x6b, 0x06, 0xef, 0x5f, 0x81, 0xfb, 0xf8, 0xbf, 0x9b, 0x0f, 0xbe, + 0xdf, 0xff, 0xc7, 0x11, 0xfd, 0x7f, 0x94, 0xd7, 0x86, 0x47, 0xa1, 0xf5, 0xfd, 0x5a, 0xfc, 0x47, 0x21, 0x66, 0xa2, 0x65, + 0x5e, 0x3e, 0x96, 0xac, 0xe5, 0xf5, 0xae, 0x19, 0xee, 0xb9, 0x5a, 0x77, 0xbd, 0x68, 0xd7, 0xac, 0xe6, 0x77, 0xc5, 0xff, + 0x68, 0xe8, 0xff, 0x9f, 0x8d, 0x01, 0x66, 0xbf, 0x47, 0x7c, 0xac, 0xf7, 0xef, 0x8c, 0xff, 0x5d, 0x2b, 0xf6, 0xeb, 0x6b, + 0x63, 0x1c, 0x71, 0x65, 0xac, 0xff, 0x77, 0x8e, 0xff, 0xeb, 0x6b, 0x00, 0xb9, 0x79, 0x4c, 0x1c, 0x1a, 0xfd, 0x95, 0x39, + 0x73, 0xd7, 0x1d, 0xfb, 0xde, 0xa7, 0x09, 0xa2, 0xf0, 0xac, 0x46, 0xcf, 0x2a, 0xce, 0xea, 0x6b, 0x90, 0xef, 0xff, 0x77, + 0xdd, 0xff, 0xbf, 0x1f, 0x73, 0x9f, 0xd1, 0xff, 0x8f, 0xdb, 0xde, 0xfd, 0x94, 0xf3, 0x01, 0xf6, 0xb6, 0x00, 0x21, 0xfa, + 0xc1, 0x73, 0xc4, 0x80, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x9f, 0xcf, + 0xef, 0xcf, 0x63, 0x90, 0xcf, 0x84, 0xbd, 0x3e, 0x03, 0xfb, 0x4c, 0x4e, 0xe0, 0x6a, 0x09, 0x75, 0xfd, 0xc5, 0xeb, 0xdc, + 0xf1, 0x99, 0x4c, 0x0b, 0xcf, 0x6b, 0x46, 0xa4, 0x77, 0x3f, 0xe8, 0xcc, 0x28, 0x50, 0xc9, 0x69, 0x30, 0x92, 0x75, 0xa2, + 0x37, 0x97, 0x52, 0xee, 0xec, 0xf7, 0xd4, 0xf8, 0x2f, 0xc4, 0xff, 0xfb, 0x9f, 0x8c, 0x42, 0xfe, 0x9e, 0x9d, 0xf1, 0x9f, + 0xad, 0x7b, 0xb5, 0xfc, 0xb8, 0x73, 0x7d, 0xc9, 0xee, 0x8c, 0x62, 0xd1, 0x78, 0x0d, 0xf7, 0xf5, 0xab, 0x7f, 0xe6, 0xd0, + 0xd9, 0x51, 0xaf, 0x67, 0xe2, 0x3f, 0x9b, 0xfb, 0x34, 0x2e, 0x5a, 0xe1, 0x73, 0xe2, 0x7f, 0x6c, 0x89, 0xff, 0xf8, 0x48, + 0xfc, 0x77, 0x67, 0x92, 0xe9, 0xca, 0x42, 0x5e, 0xc9, 0x7c, 0x93, 0xbf, 0x86, 0xf5, 0x0c, 0xbf, 0xd5, 0x1a, 0xf7, 0x9d, + 0xf8, 0xaf, 0xd4, 0xad, 0xeb, 0x3c, 0xab, 0xe2, 0xff, 0xbe, 0x6c, 0x2b, 0xbd, 0xc9, 0xfc, 0xcf, 0x7b, 0x22, 0x7f, 0x4f, + 0x1e, 0xdd, 0xbb, 0x7d, 0xb4, 0xd6, 0xd6, 0x95, 0xd9, 0xbe, 0xf2, 0x2b, 0xf1, 0x1f, 0xc5, 0xbf, 0x12, 0x37, 0x2d, 0x4a, + 0x6f, 0x06, 0xf6, 0x9e, 0xac, 0x6d, 0x7b, 0xe3, 0xbf, 0x7b, 0xdf, 0x9f, 0x33, 0xe2, 0xff, 0xfd, 0x3c, 0xba, 0xab, 0xe6, + 0x70, 0xbd, 0x7d, 0xe5, 0xef, 0xee, 0xff, 0xef, 0x5b, 0xc9, 0x1d, 0xf5, 0x63, 0xa6, 0xa7, 0x7f, 0xb3, 0xff, 0x7f, 0xef, + 0xe7, 0xef, 0xc6, 0x7f, 0x57, 0x6d, 0x8f, 0x8d, 0x23, 0x93, 0x6a, 0x5f, 0xb9, 0x73, 0x95, 0x6c, 0xf7, 0xfc, 0x7f, 0xd5, + 0x37, 0x1b, 0xe9, 0xd1, 0x44, 0x1c, 0x1b, 0xff, 0x33, 0xab, 0x90, 0xeb, 0xc6, 0x0b, 0xef, 0x8c, 0xff, 0x6b, 0xfb, 0xdb, + 0x56, 0x76, 0x05, 0x3c, 0xad, 0xff, 0xdf, 0x75, 0x97, 0x6c, 0x6c, 0x8e, 0xc9, 0xbd, 0xc7, 0xea, 0xdc, 0xa5, 0x2e, 0x9f, + 0xbf, 0x3f, 0x9a, 0x47, 0x78, 0x5f, 0x8f, 0xff, 0xfa, 0x2a, 0xff, 0x68, 0x5a, 0x95, 0x7c, 0x6f, 0xfc, 0xbf, 0x7b, 0xa7, + 0x93, 0xca, 0x51, 0x7a, 0xda, 0xdf, 0x67, 0x77, 0x85, 0x7b, 0x7b, 0x81, 0x9f, 0xef, 0x36, 0x44, 0x53, 0xcf, 0xd1, 0x95, + 0xbf, 0xff, 0xcf, 0x73, 0xcb, 0xef, 0x78, 0x96, 0xbf, 0x13, 0x1f, 0xc9, 0xfd, 0xf4, 0xdf, 0x88, 0xff, 0xca, 0x91, 0x2b, + 0xff, 0x2b, 0x7b, 0x0d, 0x3b, 0xe3, 0xff, 0xbd, 0x16, 0x40, 0xde, 0x4d, 0x38, 0x71, 0x14, 0x03, 0xfc, 0x9e, 0x16, 0xc0, + 0x0e, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xee, 0x37, 0xb1, 0x7f, 0xce, + 0xcd, 0x55, 0xc9, 0x1a, 0x9f, 0x7f, 0x4f, 0x7a, 0x67, 0xc6, 0xfc, 0x71, 0x93, 0x85, 0xec, 0xfa, 0xb7, 0xfe, 0xfc, 0xa4, + 0x23, 0x1f, 0x6f, 0x34, 0xbe, 0xb5, 0x9e, 0xc9, 0xe5, 0x1d, 0x85, 0x6c, 0x03, 0x57, 0x6f, 0xc6, 0xe6, 0xae, 0x41, 0xee, + 0x18, 0xd1, 0x9e, 0xcf, 0x2c, 0xd2, 0xd9, 0x63, 0xfe, 0xf7, 0x9c, 0x9f, 0x67, 0xfa, 0x8d, 0xf2, 0x9b, 0xc6, 0x77, 0xb9, + 0xcc, 0xf2, 0xf9, 0x11, 0x2a, 0x39, 0xf3, 0xf2, 0x79, 0x36, 0x4f, 0xcf, 0x98, 0x3f, 0x9f, 0x79, 0x27, 0x6e, 0xea, 0xe4, + 0xf3, 0x5a, 0xfa, 0x24, 0x73, 0x44, 0x4f, 0x06, 0xa1, 0xda, 0x37, 0x8e, 0xe9, 0xda, 0x5a, 0xb9, 0xde, 0x99, 0x23, 0xc4, + 0xf4, 0xfb, 0xfc, 0x95, 0xfa, 0x1e, 0x93, 0xd7, 0xbd, 0xfe, 0x8d, 0x67, 0xb2, 0x10, 0x44, 0xa9, 0xa7, 0xeb, 0x2c, 0xc1, + 0xeb, 0x4f, 0xf2, 0x19, 0x33, 0xe3, 0x61, 0x7c, 0x75, 0xff, 0x74, 0x24, 0xdb, 0x9b, 0x4a, 0xbe, 0xba, 0x99, 0x5d, 0x33, + 0xe6, 0xc6, 0x88, 0xe3, 0x22, 0xbb, 0x52, 0x3e, 0xfa, 0x7f, 0xee, 0x6f, 0x62, 0x32, 0xfe, 0xa3, 0x98, 0x59, 0xa8, 0x1e, + 0x23, 0xd9, 0xf8, 0x9f, 0xbf, 0x0a, 0xbb, 0xe2, 0xbf, 0x92, 0xf5, 0xe0, 0xfd, 0x8c, 0xd9, 0xb5, 0x9e, 0x70, 0x6f, 0xfc, + 0xc7, 0x83, 0xf6, 0x6b, 0xa4, 0xe7, 0x2f, 0xa3, 0x90, 0xe1, 0xfc, 0xf9, 0xec, 0x22, 0x5a, 0xfb, 0xff, 0x71, 0x59, 0x26, + 0xf1, 0xc2, 0xf8, 0xbf, 0xb6, 0xdf, 0x5b, 0x2e, 0xfe, 0x67, 0x5a, 0xa7, 0xfd, 0xfd, 0x7f, 0xad, 0x5f, 0x3b, 0xb9, 0xff, + 0x7f, 0x27, 0x63, 0x76, 0x14, 0xe6, 0xd9, 0x95, 0xe3, 0xc6, 0xe4, 0xc8, 0xe0, 0xd9, 0x5c, 0x30, 0x1a, 0xe6, 0xab, 0x1d, + 0xf1, 0xdf, 0x3f, 0xc2, 0xae, 0xcc, 0xad, 0x6a, 0xfd, 0x7f, 0xb4, 0xce, 0xff, 0x23, 0xdd, 0xce, 0xcf, 0xc5, 0x7f, 0xe7, + 0x6e, 0x4b, 0x2b, 0x32, 0xe6, 0xe6, 0x77, 0x76, 0x7c, 0xab, 0xff, 0x1f, 0xa5, 0xfe, 0xbf, 0xd6, 0x7b, 0xcd, 0xd4, 0xca, + 0xd8, 0x90, 0xeb, 0x2e, 0x37, 0xfe, 0x5f, 0x3d, 0xca, 0xce, 0xcf, 0x28, 0x63, 0x53, 0xfb, 0xd4, 0x3d, 0xf7, 0xab, 0x45, + 0x4b, 0xa4, 0xc7, 0xd7, 0x3b, 0x33, 0xe6, 0x56, 0xd6, 0x9e, 0xcf, 0x8f, 0xff, 0xce, 0x3d, 0x4c, 0x2a, 0x77, 0x17, 0xa2, + 0xa1, 0x6f, 0x5e, 0xdd, 0xff, 0xcf, 0xce, 0xff, 0x2b, 0xb9, 0xf4, 0xf7, 0xac, 0xff, 0x8d, 0xa5, 0x6b, 0x3f, 0xd1, 0x7a, + 0x3f, 0x71, 0x1c, 0xd0, 0x7e, 0x77, 0x66, 0x89, 0x7f, 0x63, 0xfe, 0xdf, 0x95, 0x81, 0x7d, 0x94, 0xef, 0x09, 0xd5, 0xd7, + 0xff, 0x67, 0x6b, 0x60, 0x1c, 0xd3, 0xf7, 0x8f, 0xc7, 0x7b, 0xe8, 0xc4, 0x4b, 0xd1, 0x7f, 0x5a, 0xfc, 0x9f, 0xf2, 0x44, + 0x43, 0x47, 0xbb, 0x15, 0x2d, 0xad, 0x5b, 0x65, 0xdf, 0x96, 0xce, 0x71, 0xd4, 0xfc, 0xfc, 0x7f, 0xcd, 0xba, 0xfa, 0x1b, + 0xc7, 0x98, 0xb9, 0x23, 0x3b, 0x1f, 0xff, 0xb1, 0xbc, 0xe4, 0xe4, 0x23, 0xfe, 0x4e, 0x2b, 0x75, 0xde, 0xf1, 0xfe, 0xe6, + 0xda, 0x13, 0x1b, 0x9f, 0xb5, 0x13, 0xa1, 0xc4, 0xd1, 0xf3, 0x21, 0xd7, 0x03, 0xfe, 0xe6, 0xf9, 0xd0, 0xdf, 0xfa, 0x1c, + 0xbc, 0x92, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x7d, 0x3b, 0xa4, 0x92, 0x59, + 0x72, 0x34, 0xbf, 0x8b, 0x9e, 0xcf, 0xad, 0x5f, 0x3d, 0xbb, 0xbb, 0xf7, 0xa5, 0x6b, 0xf9, 0x79, 0x6b, 0xe5, 0x34, 0x97, + 0x8f, 0x28, 0x97, 0x53, 0xed, 0x7e, 0xff, 0x83, 0x38, 0xf2, 0x6a, 0xde, 0x65, 0x75, 0x8d, 0x42, 0x3e, 0xd5, 0xce, 0xeb, + 0x35, 0x4a, 0x65, 0xb7, 0xab, 0x96, 0x3d, 0x7f, 0x3b, 0x34, 0x36, 0xbc, 0x4f, 0x59, 0x8b, 0x85, 0x5a, 0x6e, 0xc3, 0xb9, + 0xdc, 0x61, 0xb1, 0xf8, 0x9b, 0xe5, 0xca, 0x6f, 0x7e, 0x3f, 0x89, 0xfb, 0x6c, 0xab, 0xf1, 0xb9, 0xab, 0x59, 0xcd, 0xf8, + 0xde, 0x7d, 0xbd, 0xea, 0xb5, 0xe5, 0x84, 0x5a, 0xb6, 0x7e, 0x67, 0x89, 0x37, 0xde, 0x6e, 0xad, 0x66, 0xc4, 0x8d, 0xcf, + 0xbf, 0x73, 0x1a, 0x1f, 0xcd, 0x05, 0x50, 0xcb, 0x13, 0xb9, 0xeb, 0x5b, 0x55, 0x8e, 0x14, 0xa5, 0x3d, 0x9b, 0xc6, 0xf6, + 0xec, 0x10, 0xf9, 0xec, 0xe7, 0xa3, 0x94, 0xef, 0xb3, 0xfb, 0x93, 0xca, 0x15, 0xa9, 0xc7, 0xff, 0x55, 0x39, 0x55, 0x3e, + 0x99, 0xed, 0xff, 0xc7, 0xf2, 0x7d, 0x18, 0x4e, 0xb9, 0x9a, 0x77, 0xfd, 0x7f, 0xb4, 0xed, 0x98, 0xb4, 0xf7, 0x93, 0x1d, + 0xe5, 0xda, 0x11, 0x0b, 0xbb, 0xf6, 0xfb, 0x8b, 0x74, 0xad, 0x18, 0xe9, 0x1c, 0x7b, 0xf5, 0xd9, 0xd7, 0xae, 0x4f, 0x56, + 0xc4, 0x7f, 0x5c, 0xcc, 0x93, 0xbb, 0x77, 0x1d, 0xeb, 0xbe, 0x9a, 0x4f, 0x5a, 0x86, 0x2f, 0xc6, 0xff, 0x8e, 0xf8, 0x99, + 0x9d, 0xff, 0x47, 0x79, 0x37, 0x9d, 0x33, 0x6b, 0x4c, 0x3c, 0xcc, 0xe8, 0xfc, 0xdb, 0xe2, 0x7f, 0x6f, 0x19, 0x77, 0xb7, + 0xe6, 0xd7, 0x23, 0xec, 0xf1, 0xd1, 0xf8, 0x9f, 0xdd, 0xef, 0x6d, 0x7e, 0xee, 0x7a, 0xbf, 0xfe, 0xdf, 0xb9, 0xb2, 0x71, + 0x46, 0xfc, 0x9f, 0x12, 0xe5, 0xb9, 0x32, 0x5f, 0x1b, 0xff, 0xef, 0xc7, 0x50, 0x57, 0x1f, 0xf6, 0xde, 0xb9, 0x47, 0xdb, + 0xfc, 0xff, 0x7e, 0x97, 0xe4, 0xdc, 0xdd, 0x9d, 0xbd, 0xab, 0x49, 0x3b, 0x4b, 0xbd, 0x73, 0xcf, 0xe1, 0xa7, 0x63, 0x87, + 0xee, 0xf9, 0xee, 0x19, 0xfd, 0x7f, 0x88, 0xff, 0x05, 0x6b, 0xe1, 0x27, 0xf4, 0x92, 0xab, 0xee, 0xe5, 0xe5, 0xb3, 0xd8, + 0xf7, 0x7e, 0x32, 0x5b, 0x52, 0xf9, 0xfb, 0xff, 0x5f, 0x1f, 0xff, 0x3f, 0xd9, 0xe1, 0xe7, 0x5b, 0xf1, 0x1f, 0xc5, 0x15, + 0xc5, 0x33, 0xe2, 0x3f, 0x36, 0x8e, 0x6a, 0x57, 0xdc, 0xcb, 0x3f, 0x79, 0xfc, 0xdf, 0xbf, 0x13, 0xd4, 0xd7, 0xe3, 0xff, + 0x0b, 0x63, 0xe8, 0x35, 0x4f, 0xa1, 0xbc, 0x17, 0xff, 0x73, 0x77, 0x86, 0x4f, 0x1e, 0xff, 0x9f, 0x1e, 0xff, 0xb3, 0x57, + 0x25, 0x36, 0xcd, 0xff, 0x9f, 0x8e, 0x34, 0x7e, 0x7f, 0xfc, 0x8f, 0xad, 0x7b, 0xf7, 0xd7, 0xe7, 0x9b, 0xe3, 0xd0, 0x67, + 0x61, 0xce, 0x79, 0x8a, 0xe3, 0xfd, 0x99, 0xcd, 0xfc, 0xae, 0xa8, 0xeb, 0xef, 0xff, 0x8f, 0x65, 0xfd, 0x7f, 0xef, 0x93, + 0x2d, 0xbb, 0xee, 0xff, 0xef, 0x7d, 0xa2, 0xa8, 0xfb, 0x0c, 0xc5, 0xff, 0x37, 0xda, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xe2, 0x73, 0xf2, 0x2b, 0x72, 0xeb, 0xf7, 0xe7, 0xc3, 0x38, 0x3d, 0xb7, 0xfe, + 0xdd, 0xfb, 0x33, 0xe3, 0xd8, 0xdc, 0xfa, 0xe3, 0x51, 0x8e, 0xa2, 0xef, 0xe5, 0xd6, 0xf7, 0xee, 0xc7, 0xd3, 0xb7, 0x64, + 0xd6, 0xe5, 0xd6, 0xef, 0x7b, 0x87, 0xfa, 0xf4, 0xdc, 0xfa, 0xe3, 0x26, 0x13, 0xd1, 0xc9, 0xb9, 0xf5, 0xeb, 0x6f, 0x51, + 0x9d, 0x9a, 0x5b, 0x9f, 0xb5, 0xef, 0x4e, 0x3e, 0x6d, 0xfb, 0xe3, 0xc5, 0x6b, 0xf6, 0x46, 0x36, 0x84, 0x93, 0x6b, 0x64, + 0x3e, 0x5b, 0xd3, 0x8a, 0x16, 0xa5, 0xf3, 0x7d, 0xcd, 0xb3, 0x73, 0xeb, 0x7f, 0x3d, 0xc6, 0xeb, 0xd9, 0x58, 0xa3, 0x9c, + 0xf3, 0xbe, 0xef, 0x5d, 0xed, 0x48, 0x8d, 0x1b, 0xd7, 0xe6, 0xd6, 0xba, 0x6e, 0x47, 0xcf, 0xdd, 0x29, 0xe1, 0xdb, 0xb9, + 0xb5, 0xc5, 0x7f, 0x4f, 0xab, 0x1b, 0x4d, 0x51, 0xf7, 0xf4, 0x93, 0x7d, 0x59, 0x83, 0xfa, 0xe2, 0x3f, 0xda, 0x3f, 0xdb, + 0x9b, 0x5b, 0xab, 0xba, 0x9e, 0x20, 0xfe, 0x7f, 0x57, 0x3b, 0x90, 0x99, 0xd1, 0x3e, 0x29, 0xf7, 0x37, 0x6b, 0xc0, 0xce, + 0xdc, 0x5a, 0x5f, 0x8f, 0xff, 0x4a, 0xff, 0x20, 0xfe, 0x7f, 0xcf, 0xf8, 0xbf, 0xaf, 0x2f, 0xaf, 0xaf, 0xff, 0x7f, 0x79, + 0x6f, 0xad, 0xdf, 0x19, 0xff, 0x67, 0xe4, 0xd6, 0x15, 0xff, 0xef, 0x8c, 0xff, 0x6b, 0xfb, 0x13, 0xac, 0x58, 0xcd, 0xed, + 0x9d, 0x4d, 0x88, 0xff, 0xd5, 0x75, 0xe7, 0xa4, 0x9d, 0x21, 0xc4, 0x78, 0x7d, 0xfe, 0x5f, 0xdb, 0x13, 0xb4, 0xda, 0x02, + 0xec, 0xcb, 0xd5, 0xfa, 0x46, 0x6e, 0x7d, 0xb9, 0x75, 0x67, 0xae, 0xd7, 0xf5, 0xe8, 0xef, 0x9b, 0xf7, 0x63, 0x4e, 0x1f, + 0xff, 0xf7, 0xde, 0xab, 0xf9, 0xff, 0x35, 0xa5, 0xd5, 0x2b, 0xde, 0xab, 0xc6, 0xff, 0x2b, 0xda, 0xd8, 0x37, 0x4b, 0x63, + 0xcd, 0xbd, 0xa3, 0xfe, 0x33, 0x8c, 0xa6, 0xd1, 0x1f, 0xef, 0xb4, 0x1a, 0xb0, 0x66, 0x9c, 0xf1, 0x7e, 0x4b, 0x08, 0x7c, + 0xed, 0x49, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xf9, 0xef, 0x3f, 0xfb, 0x01, 0xd4, 0x3f, - 0xf0, 0xeb, 0xea, 0xff, 0x3f }; + 0x00, 0x00, 0x00, 0x80, 0x93, 0xfc, 0xfb, 0x9f, 0x72, 0x00, 0xf1, 0x0f, 0xfc, 0x75, 0xf1, 0xff, 0x2f }; // Font glyphs rectangles data (on atlas) static const Rectangle darkFontRecs[189] = { @@ -257,100 +257,100 @@ static const Rectangle darkFontRecs[189] = { { 218, 52, 6 , 9 }, { 232, 52, 5 , 9 }, { 245, 52, 5 , 12 }, - { 258, 52, 5 , 9 }, - { 271, 52, 5 , 10 }, - { 284, 52, 7 , 9 }, - { 299, 52, 5 , 9 }, - { 312, 52, 6 , 5 }, - { 326, 52, 5 , 3 }, - { 339, 52, 7 , 9 }, - { 354, 52, 5 , 9 }, - { 367, 52, 4 , 4 }, - { 379, 52, 5 , 7 }, - { 392, 52, 5 , 9 }, - { 405, 52, 5 , 9 }, - { 418, 52, 5 , 12 }, - { 431, 52, 5 , 9 }, - { 444, 52, 7 , 9 }, - { 459, 52, 1 , 1 }, - { 468, 52, 5 , 10 }, - { 481, 52, 5 , 9 }, - { 494, 52, 5 , 9 }, - { 4, 76, 6 , 5 }, - { 18, 76, 9 , 9 }, - { 35, 76, 9 , 7 }, - { 52, 76, 5 , 11 }, - { 65, 76, 5 , 9 }, - { 78, 76, 5 , 12 }, - { 91, 76, 5 , 12 }, - { 104, 76, 5 , 12 }, - { 117, 76, 6 , 12 }, - { 131, 76, 5 , 11 }, - { 144, 76, 5 , 13 }, - { 157, 76, 9 , 9 }, - { 174, 76, 5 , 12 }, - { 187, 76, 5 , 12 }, - { 200, 76, 5 , 12 }, - { 213, 76, 5 , 12 }, - { 226, 76, 5 , 11 }, - { 239, 76, 2 , 12 }, - { 249, 76, 2 , 12 }, - { 259, 76, 3 , 12 }, - { 270, 76, 3 , 11 }, - { 281, 76, 6 , 9 }, - { 295, 76, 6 , 12 }, - { 309, 76, 5 , 12 }, - { 322, 76, 5 , 12 }, - { 335, 76, 5 , 12 }, - { 348, 76, 6 , 12 }, - { 362, 76, 5 , 11 }, - { 375, 76, 5 , 5 }, - { 388, 76, 7 , 9 }, - { 403, 76, 5 , 12 }, - { 416, 76, 5 , 12 }, - { 429, 76, 5 , 12 }, - { 442, 76, 5 , 11 }, - { 455, 76, 5 , 12 }, - { 468, 76, 5 , 9 }, - { 481, 76, 5 , 9 }, - { 494, 76, 5 , 10 }, - { 4, 100, 5 , 10 }, - { 17, 100, 5 , 10 }, - { 30, 100, 6 , 10 }, - { 44, 100, 5 , 9 }, - { 57, 100, 5 , 11 }, - { 70, 100, 9 , 7 }, + { 258, 52, 0 , 0 }, + { 266, 52, 5 , 10 }, + { 279, 52, 7 , 9 }, + { 294, 52, 0 , 0 }, + { 302, 52, 6 , 5 }, + { 316, 52, 5 , 3 }, + { 329, 52, 7 , 9 }, + { 344, 52, 0 , 0 }, + { 352, 52, 4 , 4 }, + { 364, 52, 5 , 7 }, + { 377, 52, 0 , 0 }, + { 385, 52, 0 , 0 }, + { 393, 52, 5 , 12 }, + { 406, 52, 5 , 9 }, + { 419, 52, 7 , 9 }, + { 434, 52, 1 , 1 }, + { 443, 52, 5 , 10 }, + { 456, 52, 0 , 0 }, + { 464, 52, 0 , 0 }, + { 472, 52, 6 , 5 }, + { 486, 52, 9 , 9 }, + { 4, 76, 9 , 7 }, + { 21, 76, 5 , 11 }, + { 34, 76, 5 , 9 }, + { 47, 76, 5 , 12 }, + { 60, 76, 5 , 12 }, + { 73, 76, 5 , 12 }, + { 86, 76, 6 , 12 }, + { 100, 76, 5 , 11 }, + { 113, 76, 5 , 13 }, + { 126, 76, 9 , 9 }, + { 143, 76, 5 , 12 }, + { 156, 76, 5 , 12 }, + { 169, 76, 5 , 12 }, + { 182, 76, 5 , 12 }, + { 195, 76, 5 , 11 }, + { 208, 76, 2 , 12 }, + { 218, 76, 2 , 12 }, + { 228, 76, 3 , 12 }, + { 239, 76, 3 , 11 }, + { 250, 76, 6 , 9 }, + { 264, 76, 6 , 12 }, + { 278, 76, 5 , 12 }, + { 291, 76, 5 , 12 }, + { 304, 76, 5 , 12 }, + { 317, 76, 6 , 12 }, + { 331, 76, 5 , 11 }, + { 344, 76, 5 , 5 }, + { 357, 76, 7 , 9 }, + { 372, 76, 5 , 12 }, + { 385, 76, 5 , 12 }, + { 398, 76, 5 , 12 }, + { 411, 76, 5 , 11 }, + { 424, 76, 5 , 12 }, + { 437, 76, 5 , 9 }, + { 450, 76, 5 , 9 }, + { 463, 76, 5 , 10 }, + { 476, 76, 5 , 10 }, + { 489, 76, 5 , 10 }, + { 4, 100, 6 , 10 }, + { 18, 100, 5 , 9 }, + { 31, 100, 5 , 11 }, + { 44, 100, 9 , 7 }, + { 61, 100, 5 , 10 }, + { 74, 100, 5 , 10 }, { 87, 100, 5 , 10 }, { 100, 100, 5 , 10 }, - { 113, 100, 5 , 10 }, - { 126, 100, 5 , 10 }, - { 139, 100, 5 , 9 }, - { 152, 100, 2 , 10 }, - { 162, 100, 2 , 10 }, - { 172, 100, 3 , 10 }, - { 183, 100, 3 , 9 }, - { 194, 100, 6 , 9 }, - { 208, 100, 6 , 10 }, + { 113, 100, 5 , 9 }, + { 126, 100, 2 , 10 }, + { 136, 100, 2 , 10 }, + { 146, 100, 3 , 10 }, + { 157, 100, 3 , 9 }, + { 168, 100, 6 , 9 }, + { 182, 100, 6 , 10 }, + { 196, 100, 5 , 10 }, + { 209, 100, 5 , 10 }, { 222, 100, 5 , 10 }, - { 235, 100, 5 , 10 }, - { 248, 100, 5 , 10 }, - { 261, 100, 6 , 10 }, - { 275, 100, 5 , 9 }, - { 288, 100, 5 , 5 }, - { 301, 100, 7 , 7 }, + { 235, 100, 6 , 10 }, + { 249, 100, 5 , 9 }, + { 262, 100, 5 , 5 }, + { 275, 100, 7 , 7 }, + { 290, 100, 5 , 10 }, + { 303, 100, 5 , 10 }, { 316, 100, 5 , 10 }, - { 329, 100, 5 , 10 }, - { 342, 100, 5 , 10 }, - { 355, 100, 5 , 9 }, - { 368, 100, 5 , 12 }, - { 381, 100, 5 , 11 }, - { 394, 100, 5 , 11 }, + { 329, 100, 5 , 9 }, + { 342, 100, 5 , 12 }, + { 355, 100, 5 , 11 }, + { 368, 100, 5 , 11 }, }; // Font glyphs info data // NOTE: No glyphs.image data provided static const GlyphInfo darkFontGlyphs[189] = { - { 32, 0, 13, 4, { 0 }}, + { 32, 0, 0, 4, { 0 }}, { 33, 2, 4, 5, { 0 }}, { 34, 2, 4, 7, { 0 }}, { 35, 1, 4, 8, { 0 }}, @@ -451,25 +451,25 @@ static const GlyphInfo darkFontGlyphs[189] = { { 8364, 1, 4, 8, { 0 }}, { 165, 1, 4, 7, { 0 }}, { 352, 1, 1, 7, { 0 }}, - { 167, 2, 4, 9, { 0 }}, + { 167, 0, 0, 0, { 0 }}, { 353, 1, 3, 7, { 0 }}, { 169, 1, 4, 9, { 0 }}, - { 170, 2, 4, 9, { 0 }}, + { 170, 0, 0, 0, { 0 }}, { 171, 1, 6, 8, { 0 }}, { 172, 1, 8, 7, { 0 }}, { 174, 1, 4, 9, { 0 }}, - { 175, 2, 4, 9, { 0 }}, + { 175, 0, 0, 0, { 0 }}, { 176, 1, 4, 6, { 0 }}, { 177, 1, 6, 7, { 0 }}, - { 178, 2, 4, 9, { 0 }}, - { 179, 2, 4, 9, { 0 }}, + { 178, 0, 0, 0, { 0 }}, + { 179, 0, 0, 0, { 0 }}, { 381, 1, 1, 7, { 0 }}, { 181, 1, 6, 7, { 0 }}, { 182, 1, 4, 9, { 0 }}, { 183, 2, 8, 5, { 0 }}, { 382, 1, 3, 7, { 0 }}, - { 185, 2, 4, 9, { 0 }}, - { 186, 2, 4, 9, { 0 }}, + { 185, 0, 0, 0, { 0 }}, + { 186, 0, 0, 0, { 0 }}, { 187, 1, 6, 8, { 0 }}, { 338, 1, 4, 11, { 0 }}, { 339, 1, 6, 11, { 0 }}, diff --git a/include/raygui/dark/style_dark.png b/include/raygui/dark/style_dark.png index 05461d1..09d2439 100644 Binary files a/include/raygui/dark/style_dark.png and b/include/raygui/dark/style_dark.png differ diff --git a/include/raygui/dark/style_dark.rgs b/include/raygui/dark/style_dark.rgs index 3f5be87..1e965d9 100644 Binary files a/include/raygui/dark/style_dark.rgs and b/include/raygui/dark/style_dark.rgs differ diff --git a/include/raygui/raygui.h b/include/raygui/raygui.h index aca423a..8422fe0 100644 --- a/include/raygui/raygui.h +++ b/include/raygui/raygui.h @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raygui v4.0 - A simple and easy-to-use immediate-mode gui library +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library * * DESCRIPTION: * raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also @@ -26,7 +26,7 @@ * NOTES: * - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for * font atlas recs and glyphs, freeing that memory is (usually) up to the user, -* no unload function is explicitly provided... but note that GuiLoadStyleDefaulf() unloads +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads * by default any previously loaded font (texture, recs, glyphs). * - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions * @@ -136,11 +136,36 @@ * * #define RAYGUI_DEBUG_RECS_BOUNDS * Draw control bounds rectangles for debug -* +* * #define RAYGUI_DEBUG_TEXT_BOUNDS * Draw text bounds rectangles for debug * * VERSIONS HISTORY: +* 5.0-dev (2025) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: GuiLoadIconsFromMemory() +* ADDED: Multiple new icons +* REMOVED: GuiSpinner() from controls list, using BUTTON + VALUEBOX properties +* REMOVED: GuiSliderPro(), functionality was redundant +* REVIEWED: Controls using text labels to use LABEL properties +* REVIEWED: Replaced sprintf() by snprintf() for more safety +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: GuiProgressBar(), improved borders computing +* REVIEWED: GuiTextBox(), multiple improvements: autocursor and more +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* * 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() * ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() * ADDED: Multiple new icons, mostly compiler related @@ -246,7 +271,7 @@ * 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. * * DEPENDENCIES: -* raylib 4.6-dev Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing * * STANDALONE MODE: * By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled @@ -291,7 +316,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2023 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -313,11 +338,10 @@ #ifndef RAYGUI_H #define RAYGUI_H -#include #define RAYGUI_VERSION_MAJOR 4 -#define RAYGUI_VERSION_MINOR 0 +#define RAYGUI_VERSION_MINOR 5 #define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.0" +#define RAYGUI_VERSION "5.0-dev" #if !defined(RAYGUI_STANDALONE) #include "raylib.h" @@ -442,13 +466,12 @@ } Font; #endif - // Style property // NOTE: Used when exporting style as code for convenience typedef struct GuiStyleProp { unsigned short controlId; // Control identifier unsigned short propertyId; // Property identifier - long propertyValue; // Property value + int propertyValue; // Property value } GuiStyleProp; /* @@ -499,7 +522,7 @@ typedef enum { typedef enum { // Default -> populates to all controls when set DEFAULT = 0, - + // Basic controls LABEL, // Used also for: LABELBUTTON BUTTON, @@ -511,7 +534,7 @@ typedef enum { DROPDOWNBOX, TEXTBOX, // Used also for: TEXTBOXMULTI VALUEBOX, - SPINNER, // Uses: BUTTON, VALUEBOX + CONTROL11, LISTVIEW, COLORPICKER, SCROLLBAR, @@ -533,12 +556,12 @@ typedef enum { BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED - BORDER_WIDTH, // Control border size, 0 for no border + BORDER_WIDTH = 12, // Control border size, 0 for no border //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls - //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls - TEXT_PADDING, // Control text padding, not considering border - TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_LINE_SPACING, // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING = 13, // Control text padding, not considering border + TEXT_ALIGNMENT = 14, // Control text horizontal alignment inside control text bound (after border and padding) //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls } GuiControlProperty; @@ -547,7 +570,6 @@ typedef enum { // TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and // should be configured by user as needed while defining the UI layout - // Gui extended properties depend on control // NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) //---------------------------------------------------------------------------------- @@ -563,12 +585,12 @@ typedef enum { TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) TEXT_WRAP_MODE // Text wrap-mode inside text bounds //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline - //TEXT_DECORATION_THICK // Text decoration line thikness + //TEXT_DECORATION_THICK // Text decoration line thickness } GuiDefaultProperty; // Other possible text properties: // TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change -// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... // Label //typedef enum { } GuiLabelProperty; @@ -616,7 +638,9 @@ typedef enum { // DropdownBox typedef enum { ARROW_PADDING = 16, // DropdownBox arrow separation from border and items - DROPDOWN_ITEMS_SPACING // DropdownBox items separation + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) } GuiDropdownBoxProperty; // TextBox/TextBoxMulti/ValueBox/Spinner @@ -624,11 +648,14 @@ typedef enum { TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable } GuiTextBoxProperty; -// Spinner +// ValueBox/Spinner typedef enum { - SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width - SPIN_BUTTON_SPACING, // Spinner buttons separation -} GuiSpinnerProperty; + SPINNER_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPINNER_BUTTON_SPACING, // Spinner buttons separation +} GuiValueBoxProperty; + +// Control11 +//typedef enum { } GuiControl11Property; // ListView typedef enum { @@ -636,6 +663,8 @@ typedef enum { LIST_ITEMS_SPACING, // ListView items separation SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_NORMAL, // ListView items border enabled in normal state + LIST_ITEMS_BORDER_WIDTH // ListView items border width } GuiListViewProperty; // ColorPicker @@ -699,7 +728,6 @@ RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position #endif - // Controls //---------------------------------------------------------------------------------------------------------- // Container/separator controls, useful for controls organization @@ -711,29 +739,30 @@ RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *act RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control // Basic controls set -RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control, shows text +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked -RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, show true when clicked -RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control, returns true when active -RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control, returns active toggle index -RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active -RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control, returns selected item index +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control -RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control, returns selected item -RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control, returns selected value +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text -RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control, returns selected value -RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control, returns selected value -RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control, shows current progress value +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders -RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control, returns mouse cell position +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control // Advance controls set -RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control, returns selected list item index +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret @@ -742,10 +771,9 @@ RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) -RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that returns HSV color value, used by GuiColorPickerHSV() +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() //---------------------------------------------------------------------------------------------------------- - #if !defined(RAYGUI_NO_ICONS) #if !defined(RAYGUI_CUSTOM_ICONS) @@ -973,20 +1001,20 @@ typedef enum { ICON_FOLDER = 217, ICON_FILE = 218, ICON_SAND_TIMER = 219, - ICON_220 = 220, - ICON_221 = 221, - ICON_222 = 222, - ICON_223 = 223, - ICON_224 = 224, - ICON_225 = 225, - ICON_226 = 226, - ICON_227 = 227, - ICON_228 = 228, - ICON_229 = 229, - ICON_230 = 230, - ICON_231 = 231, - ICON_232 = 232, - ICON_233 = 233, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, + ICON_HOT = 228, + ICON_LABEL = 229, + ICON_NAME_ID = 230, + ICON_SLICING = 231, + ICON_MANUAL_CONTROL = 232, + ICON_COLLISION = 233, ICON_234 = 234, ICON_235 = 235, ICON_236 = 236, @@ -1028,7 +1056,8 @@ typedef enum { #if defined(RAYGUI_IMPLEMENTATION) -#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // required for: isspace() [GuiTextBox()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), snprintf(), vsnprintf() [GuiLoadStyle(), GuiLoadIcons()] #include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] #include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() #include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] @@ -1291,20 +1320,20 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_220 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_221 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_222 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_223 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_224 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_225 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_226 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_227 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_228 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS + 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT + 0x00000000, 0x20803f00, 0x2a202e40, 0x20082e10, 0x08021004, 0x02040402, 0x00900108, 0x00000060, // ICON_LABEL + 0x00000000, 0x042007e0, 0x47e27c3e, 0x4ffa4002, 0x47fa4002, 0x4ffa4002, 0x7ffe4002, 0x00000000, // ICON_NAME_ID + 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING + 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL + 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 @@ -1329,7 +1358,7 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 }; -// NOTE: We keep a pointer to the icons array, useful to point to other sets if required +// NOTE: A pointer to current icons array should be defined static unsigned int *guiIconsPtr = guiIcons; #endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS @@ -1364,13 +1393,12 @@ static unsigned int guiIconScale = 1; // Gui icon default scale (if ic static bool guiTooltip = false; // Tooltip enabled/disabled static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) -static bool guiSliderDragging = false; // Gui slider drag state (no inputs processed except dragged slider) -static Rectangle guiSliderActive = { 0 }; // Gui slider active bounds rectangle, used as an unique identifier +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking -static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down -static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement +static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) //---------------------------------------------------------------------------------- // Style data array for all gui style properties (allocated on data segment by default) @@ -1451,6 +1479,7 @@ static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) @@ -1570,6 +1599,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 #endif + #if !defined(RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT) + #define RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT 18 + #endif + int result = 0; //GuiState state = guiState; @@ -1578,9 +1611,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + const float vPadding = statusBarHeight/2.0f - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT/2.0f; Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; - Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, - statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT - vPadding, + statusBar.y + vPadding, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT }; // Update control //-------------------------------------------------------------------- @@ -1621,9 +1655,9 @@ int GuiGroupBox(Rectangle bounds, const char *text) // Draw control //-------------------------------------------------------------------- - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); //-------------------------------------------------------------------- @@ -1634,7 +1668,7 @@ int GuiGroupBox(Rectangle bounds, const char *text) // Line control int GuiLine(Rectangle bounds, const char *text) { - #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #if !defined(RAYGUI_LINE_MARGIN_TEXT) #define RAYGUI_LINE_MARGIN_TEXT 12 #endif #if !defined(RAYGUI_LINE_TEXT_PADDING) @@ -1644,7 +1678,7 @@ int GuiLine(Rectangle bounds, const char *text) int result = 0; GuiState state = guiState; - Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR)); + Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); // Draw control //-------------------------------------------------------------------- @@ -1692,8 +1726,8 @@ int GuiPanel(Rectangle bounds, const char *text) //-------------------------------------------------------------------- if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BORDER_COLOR_DISABLED: LINE_COLOR)), - GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BASE_COLOR_DISABLED : (int)BACKGROUND_COLOR))); //-------------------------------------------------------------------- return result; @@ -1745,6 +1779,9 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) if (toggle) *active = i; } + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); @@ -1776,10 +1813,10 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector { #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 - + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 + int result = 0; GuiState state = guiState; - float mouseWheelSpeed = 20.0f; // Default movement speed with mouse wheel Rectangle temp = { 0 }; if (view == NULL) view = &temp; @@ -1807,31 +1844,22 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; - Rectangle horizontalScrollBar = { - (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)horizontalScrollBarWidth + Rectangle horizontalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)horizontalScrollBarWidth }; - Rectangle verticalScrollBar = { - (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), - (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)verticalScrollBarWidth, - (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + Rectangle verticalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), + (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)verticalScrollBarWidth, + (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) }; // Make sure scroll bars have a minimum width/height - // NOTE: If content >>> bounds, size could be very small or even 0 - if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) - { - horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; - mouseWheelSpeed = 30.0f; // TODO: Calculate speed increment based on content.height vs bounds.height - } - if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) - { - verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; - mouseWheelSpeed = 30.0f; // TODO: Calculate speed increment based on content.width vs bounds.width - } + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; // Calculate view area (area without the scrollbars) *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? @@ -1874,9 +1902,14 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector #endif float wheelMove = GetMouseWheelMove(); + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + // Horizontal and vertical scrolling with mouse wheel - if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed; - else scrollPos.y += wheelMove*mouseWheelSpeed; // Vertical scroll + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll } } @@ -1922,7 +1955,7 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector } // Draw scrollbar lines depending on current state - GuiDrawRectangle(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); // Set scrollbar slider size back to the way it was before GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); @@ -1960,7 +1993,7 @@ int GuiButton(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -1998,7 +2031,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2021,7 +2054,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) return pressed; } -// Toggle Button control, returns true when active +// Toggle Button control int GuiToggle(Rectangle bounds, const char *text, bool *active) { int result = 0; @@ -2032,7 +2065,7 @@ int GuiToggle(Rectangle bounds, const char *text, bool *active) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2069,7 +2102,7 @@ int GuiToggle(Rectangle bounds, const char *text, bool *active) return result; } -// Toggle Group control, returns toggled button codepointIndex +// Toggle Group control int GuiToggleGroup(Rectangle bounds, const char *text, int *active) { #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) @@ -2118,7 +2151,7 @@ int GuiToggleGroup(Rectangle bounds, const char *text, int *active) return result; } -// Toggle Slider control extended, returns true when clicked +// Toggle Slider control extended int GuiToggleSlider(Rectangle bounds, const char *text, int *active) { int result = 0; @@ -2131,7 +2164,9 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) // Get substrings items from text (items pointers) int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle slider = { 0, // Calculated later depending on the active toggle @@ -2156,7 +2191,7 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) } else state = STATE_FOCUSED; } - + if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; } @@ -2190,7 +2225,7 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) return result; } -// Check Box control, returns true when active +// Check Box control, returns 1 when state changed int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) { int result = 0; @@ -2212,7 +2247,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2229,7 +2264,11 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; else state = STATE_FOCUSED; - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) *checked = !(*checked); + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + *checked = !(*checked); + result = 1; + } } } //-------------------------------------------------------------------- @@ -2253,7 +2292,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) return result; } -// Combo Box control, returns selected item codepointIndex +// Combo Box control int GuiComboBox(Rectangle bounds, const char *text, int *active) { int result = 0; @@ -2276,7 +2315,7 @@ int GuiComboBox(Rectangle bounds, const char *text, int *active) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2324,21 +2363,28 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod int result = 0; GuiState state = guiState; + int temp = 0; + if (active == NULL) active = &temp; + int itemSelected = *active; int itemFocused = -1; + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + // Get substrings items from text (items pointers, lengths and count) int itemCount = 0; const char **items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle boundsOpen = bounds; boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); Rectangle itemBounds = bounds; // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiSliderDragging) + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2359,7 +2405,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod for (int i = 0; i < itemCount; i++) { // Update item rectangle y position for next item - itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); if (CheckCollisionPointRec(mousePoint, itemBounds)) { @@ -2403,7 +2450,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod for (int i = 0; i < itemCount; i++) { // Update item rectangle y position for next item - itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); if (i == itemSelected) { @@ -2419,14 +2467,17 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod } } - // Draw arrows (using icon if available) + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) #if defined(RAYGUI_NO_ICONS) - GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); #else - GuiDrawText("#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL #endif + } //-------------------------------------------------------------------- *active = itemSelected; @@ -2437,13 +2488,13 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod // Text Box control // NOTE: Returns true on ENTER pressed (useful for data validation) -int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 30 // Frames to wait for autocursor movement #endif #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 2 // Frames delay for autocursor movement #endif int result = 0; @@ -2453,7 +2504,10 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textWidth = GetTextWidth(text) - GetTextWidth(text + textBoxCursorIndex); + int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); int textIndexOffset = 0; // Text index offset to start drawing in the box // Cursor rectangle @@ -2474,15 +2528,6 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) mouseCursor.x = -1; mouseCursor.width = 1; - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else - { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter - } - // Blink-cursor frame counter //if (!autoCursorMode) blinkCursorFrameCounter++; //else blinkCursorFrameCounter = 0; @@ -2493,15 +2538,24 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if ((state != STATE_DISABLED) && // Control not disabled !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode !guiLocked && // Gui not locked - !guiSliderDragging && // No gui slider on dragging + !guiControlExclusiveMode && // No gui slider on dragging (wrapMode == TEXT_WRAP_NONE)) // No wrap mode { Vector2 mousePosition = GetMousePosition(); if (editMode) { + // GLOBAL: Auto-cursor movement logic + // NOTE: Keystrokes are handled repeatedly when button is held down for some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; + else autoCursorCounter = 0; + + bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); + state = STATE_PRESSED; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + // If text does not fit in the textbox and current cursor position is out of bounds, // we add an index offset to text for drawing only what requires depending on cursor while (textWidth >= textBounds.width) @@ -2514,19 +2568,47 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); } - int textLength = (int)strlen(text); // Get current text length int codepoint = GetCharPressed(); // Get Unicode codepoint if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - // Encode codepoint as UTF-8 int codepointSize = 0; const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + // Handle Paste action + if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + const char *pasteText = GetClipboardText(); + if (pasteText != NULL) + { + int pasteLength = 0; + int pasteCodepoint; + int pasteCodepointSize; + // count how many codepoints to copy, stopping at the first unwanted control character + while (true) + { + pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); + if (textLength + pasteLength + pasteCodepointSize >= textSize) break; + if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; + pasteLength += pasteCodepointSize; + } + if (pasteLength > 0) + { + // Move forward data from cursor position + for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; + + // Paste data in at cursor + for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i]; + + textBoxCursorIndex += pasteLength; + textLength += pasteLength; + text[textLength] = '\0'; + } + } + } // Add codepoint to text, at current cursor position // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < bufferSize)) + else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) { // Move forward data from cursor position for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; @@ -2547,89 +2629,190 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) // Move cursor to end if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + // Delete related codepoints from text, after current cursor position + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - - textLength -= codepointSize; - - // Make sure text last character is EOL - text[textLength] = '\0'; + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + // Check whitespace to delete (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Move text after cursor forward (including final null terminator) + for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + } + // Delete single codepoint from text, after current cursor position + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; + + textLength -= nextCodepointSize; } - // Delete codepoint from text, before current cursor position - if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + // Delete related codepoints from text, before current cursor position + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check whitespace to delete (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; } + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + textBoxCursorIndex -= accCodepointSize; + } + // Delete single codepoint from text, before current cursor position + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) + { + int prevCodepointSize = 0; + + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; + + textLength -= prevCodepointSize; + textBoxCursorIndex -= prevCodepointSize; } // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check whitespace to skip (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; } + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + textBoxCursorIndex = offset; } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + textBoxCursorIndex -= prevCodepointSize; + } + else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to skip (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + textBoxCursorIndex = offset; + } + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + textBoxCursorIndex += nextCodepointSize; } // Move cursor position with mouse if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text { float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; - int codepoint = 0; - int codepointSize = 0; int codepointIndex = 0; float glyphWidth = 0.0f; float widthToMouseX = 0; int mouseCursorIndex = 0; - for (int i = textIndexOffset; i < textLength; i++) + for (int i = textIndexOffset; i < textLength; i += codepointSize) { codepoint = GetCodepointNext(&text[i], &codepointSize); codepointIndex = GetGlyphIndex(guiFont, codepoint); @@ -2643,7 +2826,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) mouseCursorIndex = i; break; } - + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); } @@ -2652,7 +2835,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) { mouseCursor.x = textBounds.x + textEndWidth; - mouseCursorIndex = strlen(text); + mouseCursorIndex = textLength; } // Place cursor at required index on mouse click @@ -2673,6 +2856,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) { textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2684,7 +2868,8 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - textBoxCursorIndex = (int)strlen(text); // GLOBAL: Place cursor index to the end of current text + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2726,7 +2911,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) /* // Text Box control with multiple lines and word-wrap // NOTE: This text-box is readonly, no editing supported by default -bool GuiTextBoxMulti(Rectangle bounds, char *text, int bufferSize, bool editMode) +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) { bool pressed = false; @@ -2735,12 +2920,12 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int bufferSize, bool editMode GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); // TODO: Implement methods to calculate cursor position properly - pressed = GuiTextBox(bounds, text, bufferSize, editMode); + pressed = GuiTextBox(bounds, text, textSize, editMode); GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); - + return pressed; } */ @@ -2753,24 +2938,27 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int int tempValue = *value; - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle valueBoxBounds = { + bounds.x + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING), + bounds.y, + bounds.width - 2*(GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.y, + (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; Rectangle textBounds = { 0 }; if (text != NULL) { textBounds.width = (float)GetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); } // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2799,20 +2987,20 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // Draw control //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + result = GuiValueBox(valueBoxBounds, NULL, &tempValue, minValue, maxValue, editMode); // Draw value selector custom buttons // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(VALUEBOX, BORDER_WIDTH)); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); //-------------------------------------------------------------------- *value = tempValue; @@ -2831,7 +3019,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiState state = guiState; char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); + snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); Rectangle textBounds = { 0 }; if (text != NULL) @@ -2845,7 +3033,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2857,6 +3045,34 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int keyCount = (int)strlen(textValue); + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for(int i = 0 ; i < keyCount; i++ ) + { + textValue[i] = textValue[i + 1]; + } + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS -1){ + if (keyCount == 0){ + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + for(int i = keyCount ; i > -1; i-- ) + { + textValue[i + 1] = textValue[i]; + } + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + // Only allow keys in range [48..57] if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { @@ -2889,7 +3105,13 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in //if (*value > maxValue) *value = maxValue; //else if (*value < minValue) *value = minValue; - if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } } else { @@ -2914,11 +3136,14 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - // Draw cursor + // Draw cursor rectangle if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + 2, + 2, bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2 - 4 }; + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); } @@ -2929,54 +3154,182 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in return result; } +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //snprintf(textValue, sizeof(textValue), "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0; i < keyCount; i++) + { + textValue[i] = textValue[i + 1]; + } + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS - 1) { + if (keyCount == 0) { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + for (int i = keyCount; i > -1; i--) + { + textValue[i + 1] = textValue[i]; + } + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one -int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { int result = 0; GuiState state = guiState; float temp = (maxValue - minValue)/2.0f; if (value == NULL) value = &temp; + float oldValue = *value; - int sliderValue = (int)(((*value - minValue)/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + int sliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - if (sliderWidth > 0) // Slider - { - slider.x += (sliderValue - sliderWidth/2); - slider.width = (float)sliderWidth; - } - else if (sliderWidth == 0) // SliderBar - { - slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); - slider.width = (float)sliderValue; - } - // Update control //-------------------------------------------------------------------- if ((state != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; - // Get equivalent value and slider position from mousePosition.x - *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds)) @@ -2984,16 +3337,13 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts if (!CheckCollisionPointRec(mousePoint, slider)) { // Get equivalent value and slider position from mousePosition.x - *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; - - if (sliderWidth > 0) slider.x = mousePoint.x - slider.width/2; // Slider - else if (sliderWidth == 0) slider.width = (float)sliderValue; // SliderBar + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else state = STATE_FOCUSED; @@ -3003,14 +3353,23 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, else if (*value < minValue) *value = minValue; } - // Bar limits check + // Control value change check + if (oldValue == *value) result = 0; + else result = 1; + + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); if (sliderWidth > 0) // Slider { + slider.x += sliderValue; + slider.width = (float)sliderWidth; if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); } else if (sliderWidth == 0) // SliderBar { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); } //-------------------------------------------------------------------- @@ -3023,6 +3382,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + else if (state == STATE_DISABLED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_DISABLED))); // Draw left/right text if provided if (textLeft != NULL) @@ -3033,7 +3393,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) @@ -3044,23 +3404,23 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- return result; } -// Slider control extended, returns selected value and has text -int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); -} - // Slider Bar control extended, returns selected value int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); + int result = 0; + int preSliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 0); + result = GuiSlider(bounds, textLeft, textRight, value, minValue, maxValue); + GuiSetStyle(SLIDER, SLIDER_WIDTH, preSliderWidth); + + return result; } // Progress Bar control extended, shows current progress value @@ -3075,14 +3435,14 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight // Progress bar Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, - bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + bounds.height - GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) -1 }; // Update control //-------------------------------------------------------------------- if (*value > maxValue) *value = maxValue; // WARNING: Working with floats could lead to rounding issues - if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue) ? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + if ((state != STATE_DISABLED)) progress.width = ((float)*value/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)); //-------------------------------------------------------------------- // Draw control @@ -3100,15 +3460,15 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); } - else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1}, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); else { // Draw borders not yet reached by value - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + bounds.height - 1, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); } // Draw slider internal progress bar (depends on state) @@ -3124,7 +3484,7 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) @@ -3135,7 +3495,7 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- @@ -3165,7 +3525,7 @@ int GuiDummyRec(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -3232,7 +3592,7 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -3280,14 +3640,16 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Draw control //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background // Draw visible items for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) { + if (GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_NORMAL)) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + if (state == STATE_DISABLED) { - if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); } @@ -3296,18 +3658,18 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd if (((startIndex + i) == itemSelected) && (active != NULL)) { // Draw item selected - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); } else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! { // Draw item focused - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); } else { - // Draw item normal + // Draw item normal (no rectangle) GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); } } @@ -3347,84 +3709,32 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd return result; } -// Color Panel control +// Color Panel control - Color (RGBA) variant int GuiColorPanel(Rectangle bounds, const char *text, Color *color) { int result = 0; - GuiState state = guiState; - Vector2 pickerSelector = { 0 }; - - const Color colWhite = { 255, 255, 255, 255 }; - const Color colBlack = { 0, 0, 0, 255 }; Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv - pickerSelector.x = bounds.x + (float)hsv.y*bounds.width; // HSV: Saturation - pickerSelector.y = bounds.y + (1.0f - (float)hsv.z)*bounds.height; // HSV: Value + GuiColorPanelHSV(bounds, text, &hsv); - float hue = -1.0f; - Vector3 maxHue = { hue >= 0.0f ? hue : hsv.x, 1.0f, 1.0f }; - Vector3 rgbHue = ConvertHSVtoRGB(maxHue); - Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), - (unsigned char)(255.0f*rgbHue.y), - (unsigned char)(255.0f*rgbHue.z), 255 }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + // Check if the hsv was changed, only then change the color + // This is required, because the Color->HSV->Color conversion has precision errors + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value + // Otherwise GuiColorPanel would often modify it's color without user input + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) { - Vector2 mousePoint = GetMousePosition(); + Vector3 rgb = ConvertHSVtoRGB(hsv); - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - pickerSelector = mousePoint; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - hsv.y = colorPick.x; - hsv.z = 1.0f - colorPick.y; - - Vector3 rgb = ConvertHSVtoRGB(hsv); - - // NOTE: Vector3ToColor() only available on raylib 1.8.1 - *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), - (unsigned char)(255.0f*rgb.y), - (unsigned char)(255.0f*rgb.z), - (unsigned char)(255.0f*(float)color->a/255.0f) }; - - } - else state = STATE_FOCUSED; - } + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); - DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); - - // Draw color picker: selector - Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; - GuiDrawRectangle(selector, 0, BLANK, colWhite); - } - else - { - DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); - } - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - //-------------------------------------------------------------------- - return result; } @@ -3438,7 +3748,10 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) int result = 0; GuiState state = guiState; - Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, + (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), + (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), + (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; // Update control //-------------------------------------------------------------------- @@ -3446,11 +3759,11 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -3461,8 +3774,8 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) @@ -3470,8 +3783,8 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts *alpha = (mousePoint.x - bounds.x)/bounds.width; if (*alpha <= 0.0f) *alpha = 0.0f; @@ -3485,7 +3798,6 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) // Draw control //-------------------------------------------------------------------- - // Draw alpha bar: checked background if (state != STATE_DISABLED) { @@ -3532,11 +3844,11 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -3547,8 +3859,8 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) @@ -3556,8 +3868,8 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts *hue = (mousePoint.y - bounds.y)*360/bounds.height; if (*hue <= 0.0f) *hue = 0.0f; @@ -3610,6 +3922,7 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) // float GuiColorBarAlpha(Rectangle bounds, float alpha) // float GuiColorBarHue(Rectangle bounds, float value) // NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead int GuiColorPicker(Rectangle bounds, const char *text, Color *color) { int result = 0; @@ -3622,6 +3935,7 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); GuiColorBarHue(boundsHue, NULL, &hsv.x); @@ -3634,8 +3948,8 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) return result; } -// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. -// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB // NOTE: It's divided in multiple controls: // int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) @@ -3663,8 +3977,7 @@ int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) return result; } -// Color Panel control, returns HSV color value in *colorHsv. -// Used by GuiColorPickerHSV() +// Color Panel control - HSV variant int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) { int result = 0; @@ -3677,8 +3990,7 @@ int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value - float hue = -1.0f; - Vector3 maxHue = { hue >= 0.0f ? hue : colorHsv->x, 1.0f, 1.0f }; + Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; Vector3 rgbHue = ConvertHSVtoRGB(maxHue); Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), (unsigned char)(255.0f*rgbHue.y), @@ -3686,15 +3998,47 @@ int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); - if (CheckCollisionPointRec(mousePoint, bounds)) + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; pickerSelector = mousePoint; // Calculate color from picker @@ -3753,12 +4097,12 @@ int GuiMessageBox(Rectangle bounds, const char *title, const char *message, cons buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - int textWidth = GetTextWidth(message) + 2; + //int textWidth = GetTextWidth(message) + 2; Rectangle textBounds = { 0 }; - textBounds.x = bounds.x + bounds.width/2 - textWidth/2; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.width = (float)textWidth; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; // Draw control @@ -3891,15 +4235,17 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect GuiState state = guiState; Vector2 mousePoint = GetMousePosition(); - Vector2 currentMouseCell = { 0 }; + Vector2 currentMouseCell = { -1, -1 }; float spaceWidth = spacing/(float)subdivs; int linesV = (int)(bounds.width/spaceWidth) + 1; int linesH = (int)(bounds.height/spaceWidth) + 1; + int color = GuiGetStyle(DEFAULT, LINE_COLOR); + // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { if (CheckCollisionPointRec(mousePoint, bounds)) { @@ -3912,28 +4258,23 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect // Draw control //-------------------------------------------------------------------- - switch (state) - { - case STATE_NORMAL: - { - if (subdivs > 0) - { - // Draw vertical grid lines - for (int i = 0; i < linesV; i++) - { - Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height }; - GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), RAYGUI_GRID_ALPHA)); - } + if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); - // Draw horizontal grid lines - for (int i = 0; i < linesH; i++) - { - Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width, 1 }; - GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), RAYGUI_GRID_ALPHA)); - } - } - } break; - default: break; + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } } if (mouseCell != NULL) *mouseCell = currentMouseCell; @@ -3953,7 +4294,6 @@ void GuiDisableTooltip(void) { guiTooltip = false; } // Set tooltip string void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } - //---------------------------------------------------------------------------------- // Styles loading functions //---------------------------------------------------------------------------------- @@ -3966,6 +4306,7 @@ void GuiLoadStyle(const char *fileName) #define MAX_LINE_BUFFER_SIZE 256 bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); // Try reading the files as text file first FILE *rgsFile = fopen(fileName, "rt"); @@ -4098,7 +4439,7 @@ void GuiLoadStyleDefault(void) GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); GuiSetStyle(DEFAULT, TEXT_PADDING, 0); GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - + // Initialize default extended property values // NOTE: By default, extended property values are initialized to 0 GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls @@ -4122,8 +4463,6 @@ void GuiLoadStyleDefault(void) GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(SPINNER, TEXT_PADDING, 0); - GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); @@ -4138,8 +4477,8 @@ void GuiLoadStyleDefault(void) GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); - GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); - GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH, 24); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_SPACING, 2); GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); @@ -4149,6 +4488,7 @@ void GuiLoadStyleDefault(void) GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH, 1); GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); @@ -4161,8 +4501,8 @@ void GuiLoadStyleDefault(void) { // Unload previous font texture UnloadTexture(guiFont.texture); - RL_FREE(guiFont.recs); - RL_FREE(guiFont.glyphs); + RAYGUI_FREE(guiFont.recs); + RAYGUI_FREE(guiFont.glyphs); guiFont.recs = NULL; guiFont.glyphs = NULL; @@ -4186,12 +4526,12 @@ const char *GuiIconText(int iconId, const char *text) return NULL; #else static char buffer[1024] = { 0 }; - static char iconBuffer[6] = { 0 }; + static char iconBuffer[16] = { 0 }; if (text != NULL) { memset(buffer, 0, 1024); - sprintf(buffer, "#%03i#", iconId); + snprintf(buffer, 1024, "#%03i#", iconId); for (int i = 5; i < 1024; i++) { @@ -4203,7 +4543,7 @@ const char *GuiIconText(int iconId, const char *text) } else { - sprintf(iconBuffer, "#%03i#", iconId & 0x1ff); + snprintf(iconBuffer, 16, "#%03i#", iconId); return iconBuffer; } @@ -4279,7 +4619,7 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); // Read icons data directly over internal icons array - fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + fread(guiIconsPtr, sizeof(unsigned int), (int)iconCount*((int)iconSize*(int)iconSize/32), rgiFile); } fclose(rgiFile); @@ -4288,6 +4628,56 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) return guiIconsName; } +// Load icons from memory +// WARNING: Binary files only +char **GuiLoadIconsFromMemory(const unsigned char *fileData, int dataSize, bool loadIconsName) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + char **guiIconsName = NULL; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&iconCount, fileDataPtr + 4 + 2 + 2, sizeof(short)); + memcpy(&iconSize, fileDataPtr + 4 + 2 + 2 + 2, sizeof(short)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + memcpy(guiIconsName[i], fileDataPtr, RAYGUI_ICON_MAX_NAME_LENGTH); + fileDataPtr += RAYGUI_ICON_MAX_NAME_LENGTH; + } + } + else + { + // Skip icon name data if not required + fileDataPtr += iconCount*RAYGUI_ICON_MAX_NAME_LENGTH; + } + + int iconDataSize = iconCount*((int)iconSize*(int)iconSize/32)*(int)sizeof(unsigned int); + guiIconsPtr = (unsigned int *)RAYGUI_MALLOC(iconDataSize); + + memcpy(guiIconsPtr, fileDataPtr, iconDataSize); + } + + return guiIconsName; +} + // Draw selected icon using rectangles pixel-by-pixel void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) { @@ -4360,7 +4750,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) // NOTE: All DEFAULT properties should be defined first in the file GuiSetStyle(0, (int)propertyId, propertyValue); - if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) GuiSetStyle(i, (int)propertyId, propertyValue); + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); } else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); } @@ -4599,7 +4989,7 @@ static int GetTextWidth(const char *text) } } - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE - ICON_TEXT_PADDING); + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); } return (int)textSize.x; @@ -4625,7 +5015,7 @@ static Rectangle GetTextBounds(int control, Rectangle bounds) case SLIDER: case CHECKBOX: case VALUEBOX: - case SPINNER: + case CONTROL11: // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER default: { @@ -4671,7 +5061,7 @@ static const char *GetTextIcon(const char *text, int *iconId) } // Get text divided into lines (by line-breaks '\n') -const char **GetTextLines(const char *text, int *count) +static const char **GetTextLines(const char *text, int *count) { #define RAYGUI_MAX_TEXT_LINES 128 @@ -4772,6 +5162,7 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C // Get text position depending on alignment and iconId //--------------------------------------------------------------------------------- Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; // NOTE: We get text size after icon has been processed // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? @@ -4797,6 +5188,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C default: break; } + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + switch (alignmentVertical) { // Only valid in case of wordWrap = 0; @@ -4819,7 +5212,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C { // NOTE: We consider icon height, probably different than text size GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); - textBoundsPosition.x += (RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); } #endif // Get size in bytes of text, @@ -4828,9 +5222,15 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + int textOffsetY = 0; float textOffsetX = 0.0f; float glyphWidth = 0; + + int ellipsisWidth = GetTextWidth("..."); + bool textOverflow = false; for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) { int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); @@ -4838,54 +5238,85 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size - // Wrap mode text measuring to space to validate if it can be drawn or - // a new line is required + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required if (wrapMode == TEXT_WRAP_CHAR) { - // Get glyph width to check if it goes out of bounds - if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); - else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - // Jump to next line if current character reach end of the box limits - if ((textOffsetX + glyphWidth) > textBounds.width) + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) { textOffsetX = 0.0f; textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } } } else if (wrapMode == TEXT_WRAP_WORD) { + if (codepoint == 32) lastSpaceIndex = c; + // Get width to next space in line int nextSpaceIndex = 0; float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); - if ((textOffsetX + nextSpaceWidth) > textBounds.width) + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) { textOffsetX = 0.0f; textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); } - - // TODO: Consider case: (nextSpaceWidth >= textBounds.width) } if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint else { - // TODO: There are multiple types of spaces in Unicode, + // TODO: There are multiple types of spaces in Unicode, // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph { if (wrapMode == TEXT_WRAP_NONE) { // Draw only required text glyphs fitting the textBounds.width - if (textOffsetX <= (textBounds.width - glyphWidth)) + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else { DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); } } - else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) { // Draw only glyphs inside the bounds if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) @@ -4936,19 +5367,19 @@ static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, // Draw tooltip using control bounds static void GuiTooltip(Rectangle controlRec) { - if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiSliderDragging) + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) { Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); - GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, NULL); int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); GuiSetStyle(LABEL, TEXT_PADDING, 0); GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, guiTooltipPtr); GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); GuiSetStyle(LABEL, TEXT_PADDING, textPadding); } @@ -5002,7 +5433,7 @@ static const char **GuiTextSplit(const char *text, char delimiter, int *count, i buffer[i] = '\0'; // Set an end of string at this point counter++; - if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + if (counter >= RAYGUI_TEXTSPLIT_MAX_ITEMS) break; } } @@ -5162,8 +5593,11 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) if (value > maxValue) value = maxValue; if (value < minValue) value = minValue; - const int valueRange = maxValue - minValue; + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size // Calculate rectangles for all of the components arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ @@ -5204,13 +5638,13 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && !CheckCollisionPointRec(mousePoint, arrowUpLeft) && !CheckCollisionPointRec(mousePoint, arrowDownRight)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -5220,8 +5654,8 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds)) @@ -5235,8 +5669,8 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) // Handle mouse button down if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts // Check arrows click if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); @@ -5321,10 +5755,10 @@ static Color GetColor(int hexValue) { Color color; - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; return color; } @@ -5357,7 +5791,7 @@ static const char *TextFormat(const char *text, ...) va_list args; va_start(args, text); - vsprintf(buffer, text, args); + vsnprintf(buffer, RAYGUI_TEXTFORMAT_MAX_SIZE, text, args); va_end(args); return buffer; @@ -5436,6 +5870,37 @@ static int TextToInteger(const char *text) return value*sign; } +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + // Encode codepoint into UTF-8 text (char array size returned as parameter) static const char *CodepointToUTF8(int codepoint, int *byteSize) { @@ -5489,21 +5954,21 @@ static int GetCodepointNext(const char *text, int *codepointSize) if (0xf0 == (0xf8 & ptr[0])) { // 4 byte UTF-8 codepoint - if(((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); *codepointSize = 4; } else if (0xe0 == (0xf0 & ptr[0])) { // 3 byte UTF-8 codepoint */ - if(((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); *codepointSize = 3; } else if (0xc0 == (0xe0 & ptr[0])) { // 2 byte UTF-8 codepoint - if((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); *codepointSize = 2; } @@ -5514,7 +5979,6 @@ static int GetCodepointNext(const char *text, int *codepointSize) *codepointSize = 1; } - return codepoint; } #endif // RAYGUI_STANDALONE diff --git a/include/raylib.h b/include/raylib.h index e0c74af..f42ab40 100644 --- a/include/raylib.h +++ b/include/raylib.h @@ -1,22 +1,22 @@ /********************************************************************************************** * -* raylib v5.5-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) * * FEATURES: * - NO external dependencies, all required libraries included with raylib * - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, * MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. * - Written in plain C code (C99) in PascalCase/camelCase notation -* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3 or ES2 - choose at compile) +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) * - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] -* - Multiple Fonts formats supported (TTF, XNA fonts, AngelCode fonts) +* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) * - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) * - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! * - Flexible Materials system, supporting classic maps and PBR maps -* - Animated 3D models supported (skeletal bones animation) (IQM) +* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) * - Shaders support, including Model shaders and Postprocessing shaders * - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] -* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, XM, MOD) +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) * - VR stereo rendering with configurable HMD device parameters * - Bindings to multiple programming languages available! * @@ -27,29 +27,35 @@ * - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) * * DEPENDENCIES (included): -* [rcore] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP) -* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP) +* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input +* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading * [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management * * OPTIONAL DEPENDENCIES (included): * [rcore] msf_gif (Miles Fogle) for GIF recording * [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm * [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm +* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation +* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage * [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) * [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) -* [rtextures] stb_image_resize (Sean Barret) for image resizing algorithms +* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms +* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation * [rtext] stb_truetype (Sean Barret) for ttf fonts loading * [rtext] stb_rect_pack (Sean Barret) for rectangles packing * [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation * [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) * [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) -* [rmodels] Model3D (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) * [raudio] dr_wav (David Reid) for WAV audio file loading * [raudio] dr_flac (David Reid) for FLAC audio file loading * [raudio] dr_mp3 (David Reid) for MP3 audio file loading * [raudio] stb_vorbis (Sean Barret) for OGG audio loading * [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading * [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage * * * LICENSE: zlib/libpng @@ -57,7 +63,7 @@ * raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software: * -* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -82,9 +88,9 @@ #include // Required for: va_list - Only used by TraceLogCallback #define RAYLIB_VERSION_MAJOR 5 -#define RAYLIB_VERSION_MINOR 5 +#define RAYLIB_VERSION_MINOR 6 #define RAYLIB_VERSION_PATCH 0 -#define RAYLIB_VERSION "5.5-dev" +#define RAYLIB_VERSION "5.6-dev" // Function specifiers in case library is build/used as a shared library // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll @@ -737,7 +743,7 @@ typedef enum { GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right } GamepadButton; -// Gamepad axis +// Gamepad axes typedef enum { GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis @@ -795,7 +801,8 @@ typedef enum { SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights - SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices + SHADER_LOC_BONE_MATRICES, // Shader location: array of matrices uniform: boneMatrices + SHADER_LOC_VERTEX_INSTANCE_TX // Shader location: vertex attribute: instanceTransform } ShaderLocationIndex; #define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO @@ -811,6 +818,10 @@ typedef enum { SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d } ShaderUniformDataType; @@ -877,8 +888,7 @@ typedef enum { CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces - CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces - CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirrectangular map) + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces } CubemapLayout; // Font type, defines generation method @@ -944,7 +954,7 @@ typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data -typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data +typedef bool (*SaveFileTextCallback)(const char *fileName, const char *text); // FileIO: Save text data //------------------------------------------------------------------------------------ // Global Variables Definition @@ -965,36 +975,36 @@ RLAPI void CloseWindow(void); // Close windo RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen -RLAPI bool IsWindowHidden(void); // Check if window is currently hidden (only PLATFORM_DESKTOP) -RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP) -RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP) -RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized +RLAPI bool IsWindowFocused(void); // Check if window is currently focused RLAPI bool IsWindowResized(void); // Check if window has been resized last frame RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled -RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags (only PLATFORM_DESKTOP) +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags -RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP) -RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP) -RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) -RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) -RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) -RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit, only PLATFORM_DESKTOP) -RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit, only PLATFORM_DESKTOP) -RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB) -RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP) +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable +RLAPI void RestoreWindow(void); // Restore window from being minimized/maximized +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) +RLAPI void SetWindowTitle(const char *title); // Set title for window +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) RLAPI void SetWindowSize(int width, int height); // Set window dimensions -RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] (only PLATFORM_DESKTOP) -RLAPI void SetWindowFocused(void); // Set window focused (only PLATFORM_DESKTOP) +RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] +RLAPI void SetWindowFocused(void); // Set window focused RLAPI void *GetWindowHandle(void); // Get native window handle RLAPI int GetScreenWidth(void); // Get current screen width RLAPI int GetScreenHeight(void); // Get current screen height RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) RLAPI int GetMonitorCount(void); // Get number of connected monitors -RLAPI int GetCurrentMonitor(void); // Get current connected monitor +RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) @@ -1006,6 +1016,7 @@ RLAPI Vector2 GetWindowScaleDPI(void); // Get window RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor RLAPI void SetClipboardText(const char *text); // Set clipboard text content RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image content RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling @@ -1044,13 +1055,13 @@ RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR s // NOTE: Shader functionality is not available on OpenGL 1.1 RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations -RLAPI bool IsShaderReady(Shader shader); // Check if a shader is ready +RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) -RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value and bind the texture (sampler2d) RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) // Screen-space-related functions @@ -1112,7 +1123,7 @@ RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() -RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +RLAPI bool SaveFileText(const char *fileName, const char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success //------------------------------------------------------------------ // File system functions @@ -1142,18 +1153,21 @@ RLAPI long GetFileModTime(const char *fileName); // Get file mo // Compression/Encoding functionality RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() -RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() -RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const char *text, int *outputSize); // Decode Base64 string (expected NULL terminated), memory must be MemFree() +RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code +RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) +RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) // Automation events functionality -RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS -RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file -RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file -RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to -RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording -RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) -RLAPI void StopAutomationEventRecording(void); // Stop recording automation events -RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event +RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file +RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file +RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to +RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording +RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) +RLAPI void StopAutomationEventRecording(void); // Stop recording automation events +RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) @@ -1161,26 +1175,27 @@ RLAPI void PlayAutomationEvent(AutomationEvent event); // Input-related functions: keyboard RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once -RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again (Only PLATFORM_DESKTOP) +RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again RLAPI bool IsKeyDown(int key); // Check if a key is being pressed RLAPI bool IsKeyReleased(int key); // Check if a key has been released once RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty +RLAPI const char *GetKeyName(int key); // Get name of a QWERTY key on the current keyboard layout (eg returns string 'q' for KEY_A on an AZERTY keyboard) RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) // Input-related functions: gamepads -RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available -RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id -RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once -RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed -RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once -RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed -RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed -RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad -RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis -RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) -RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor); // Set gamepad vibration for both motors +RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) +RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) // Input-related functions: mouse RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once @@ -1208,19 +1223,19 @@ RLAPI int GetTouchPointCount(void); // Get number of t //------------------------------------------------------------------------------------ // Gestures and Touch Handling Functions (Module: rgestures) //------------------------------------------------------------------------------------ -RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags -RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected -RLAPI int GetGestureDetected(void); // Get latest detected gesture -RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds -RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector -RLAPI float GetGestureDragAngle(void); // Get gesture drag angle -RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta -RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle //------------------------------------------------------------------------------------ // Camera System Functions (Module: rcamera) //------------------------------------------------------------------------------------ -RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode +RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation //------------------------------------------------------------------------------------ @@ -1229,9 +1244,9 @@ RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, f // Set texture and rectangle to be used on shapes drawing // NOTE: It can be useful when using basic shapes and one single font, // defining a font char white rectangle would allow drawing everything in a single draw call -RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing -RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing -RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing +RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing +RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing // Basic shapes drawing functions RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] @@ -1249,7 +1264,9 @@ RLAPI void DrawCircleV(Vector2 center, float radius, Color color); RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse (Vector version) RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse outline (Vector version) RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle @@ -1258,7 +1275,7 @@ RLAPI void DrawRectangleRec(Rectangle rec, Color color); RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color bottomRight, Color topRight); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges @@ -1273,11 +1290,11 @@ RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters // Splines drawing functions -RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points -RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points -RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points -RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] -RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points +RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points +RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points @@ -1295,13 +1312,13 @@ RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vect RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference -RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] -RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision //------------------------------------------------------------------------------------ @@ -1317,7 +1334,7 @@ RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *f RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) -RLAPI bool IsImageReady(Image image); // Check if an image is ready +RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer @@ -1350,7 +1367,7 @@ RLAPI void ImageAlphaPremultiply(Image *image); RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) -RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth, int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) @@ -1391,8 +1408,8 @@ RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color c RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image -RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) -RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) @@ -1403,9 +1420,9 @@ RLAPI Texture2D LoadTexture(const char *fileName); RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) -RLAPI bool IsTextureReady(Texture2D texture); // Check if a texture is ready +RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) -RLAPI bool IsRenderTextureReady(RenderTexture2D target); // Check if a render texture is ready +RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data @@ -1452,7 +1469,7 @@ RLAPI Font LoadFont(const char *fileName); RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' -RLAPI bool IsFontReady(Font font); // Check if a font is ready +RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) @@ -1476,18 +1493,19 @@ RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found // Text codepoints management functions (unicode characters) -RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array -RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array -RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter -RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory -RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string -RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) +RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array +RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) // Text strings management functions (no UTF-8 strings, only byte chars) -// NOTE: Some strings allocate memory internally for returned strings, just be careful! +// WARNING 1: Most of these functions use internal static buffers, it's recommended to store returned data on user-side for re-use +// WARNING 2: Some strings allocate memory internally for the returned strings, those strings must be free by user using MemFree() RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending @@ -1495,18 +1513,18 @@ RLAPI const char *TextFormat(const char *text, ...); RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) -RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter -RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI char *TextJoin(char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string -RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string -RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string -RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string -RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string -RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string +RLAPI char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI char *TextToSnake(const char *text); // Get Snake case notation version of provided string +RLAPI char *TextToCamel(const char *text); // Get Camel case notation version of provided string -RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) -RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) +RLAPI int TextToInteger(const char *text); // Get integer value from text +RLAPI float TextToFloat(const char *text); // Get float value from text //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) @@ -1542,7 +1560,7 @@ RLAPI void DrawGrid(int slices, float spacing); // Model management functions RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) -RLAPI bool IsModelReady(Model model); // Check if a model is ready +RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) @@ -1585,28 +1603,28 @@ RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Material loading/unloading functions RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI bool IsMaterialReady(Material material); // Check if a material is ready +RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh // Model animations loading/unloading functions RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file -RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) +RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match -RLAPI void UpdateModelAnimationBoneMatrices(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (Note GPU skinning does not work on Mac) // Collision detection functions -RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres -RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes -RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere -RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere -RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box -RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh -RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle -RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) @@ -1623,11 +1641,11 @@ RLAPI float GetMasterVolume(void); // Get mas // Wave/Sound loading/unloading functions RLAPI Wave LoadWave(const char *fileName); // Load wave data from file RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' -RLAPI bool IsWaveReady(Wave wave); // Checks if wave data is ready +RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) RLAPI Sound LoadSound(const char *fileName); // Load sound from file RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data -RLAPI bool IsSoundReady(Sound sound); // Checks if a sound is ready +RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data RLAPI void UnloadWave(Wave wave); // Unload wave data RLAPI void UnloadSound(Sound sound); // Unload sound @@ -1653,7 +1671,7 @@ RLAPI void UnloadWaveSamples(float *samples); // Unload // Music management functions RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data -RLAPI bool IsMusicReady(Music music); // Checks if a music stream is ready +RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) RLAPI void UnloadMusicStream(Music music); // Unload music stream RLAPI void PlayMusicStream(Music music); // Start music playing RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing @@ -1670,7 +1688,7 @@ RLAPI float GetMusicTimePlayed(Music music); // Get cur // AudioStream management functions RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) -RLAPI bool IsAudioStreamReady(AudioStream stream); // Checks if an audio stream is ready +RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill @@ -1685,10 +1703,10 @@ RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data -RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' +RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives frames x 2 samples as 'float' (stereo) RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream -RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives frames x 2 samples as 'float' (stereo) RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline #if defined(__cplusplus) diff --git a/include/raymath.h b/include/raymath.h index 62d52f8..a531b3a 100644 --- a/include/raymath.h +++ b/include/raymath.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raymath v1.5 - Math functions to work with Vector2, Vector3, Matrix and Quaternions +* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions * * CONVENTIONS: * - Matrix structure is defined as row-major (memory layout) but parameters naming AND all @@ -12,7 +12,7 @@ * - Functions are always self-contained, no function use another raymath function inside, * required code is directly re-implemented inside * - Functions input parameters are always received by value (2 unavoidable exceptions) -* - Functions use always a "result" variable for return +* - Functions use always a "result" variable for return (except C++ operators) * - Functions are always defined inline * - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) * - No compound literals used to make sure libray is compatible with C++ @@ -27,10 +27,12 @@ * Define static inline functions code, so #include header suffices for use. * This may use up lots of memory. * +* #define RAYMATH_DISABLE_CPP_OPERATORS +* Disables C++ operator overloads for raymath types. * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -302,6 +304,14 @@ RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) return result; } +// Calculate two vectors cross product +RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.y - v1.y*v2.x); + + return result; +} + // Calculate distance between two vectors RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) { @@ -318,8 +328,9 @@ RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) return result; } -// Calculate angle between two vectors -// NOTE: Angle is calculated from origin point (0, 0) +// Calculate the signed angle from v1 to v2, relative to the origin (0, 0) +// NOTE: Coordinate system convention: positive X right, positive Y down, +// positive angles appear clockwise, and negative angles appear counterclockwise RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) { float result = 0.0f; @@ -1457,19 +1468,35 @@ RMAPI int Vector4Equals(Vector4 p, Vector4 q) RMAPI float MatrixDeterminant(Matrix mat) { float result = 0.0f; - +/* // Cache the matrix values (speed optimization) float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + // NOTE: It takes 72 multiplication to calculate 4x4 matrix determinant result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; +*/ + // Using Laplace expansion (https://en.wikipedia.org/wiki/Laplace_expansion), + // previous operation can be simplified to 40 multiplications, decreasing matrix + // size from 4x4 to 2x2 using minors + + // Cache the matrix values (speed optimization) + float m0 = mat.m0, m1 = mat.m1, m2 = mat.m2, m3 = mat.m3; + float m4 = mat.m4, m5 = mat.m5, m6 = mat.m6, m7 = mat.m7; + float m8 = mat.m8, m9 = mat.m9, m10 = mat.m10, m11 = mat.m11; + float m12 = mat.m12, m13 = mat.m13, m14 = mat.m14, m15 = mat.m15; + + result = (m0*((m5*(m10*m15 - m11*m14) - m9*(m6*m15 - m7*m14) + m13*(m6*m11 - m7*m10))) - + m4*((m1*(m10*m15 - m11*m14) - m9*(m2*m15 - m3*m14) + m13*(m2*m11 - m3*m10))) + + m8*((m1*(m6*m15 - m7*m14) - m5*(m2*m15 - m3*m14) + m13*(m2*m7 - m3*m6))) - + m12*((m1*(m6*m11 - m7*m10) - m5*(m2*m11 - m3*m10) + m9*(m2*m7 - m3*m6)))); return result; } @@ -2567,7 +2594,13 @@ RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotatio if (!FloatEquals(det, 0)) { clone.m0 /= s.x; + clone.m4 /= s.x; + clone.m8 /= s.x; + clone.m1 /= s.y; clone.m5 /= s.y; + clone.m9 /= s.y; + clone.m2 /= s.z; + clone.m6 /= s.z; clone.m10 /= s.z; // Extract rotation @@ -2580,4 +2613,354 @@ RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotatio } } +#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) + +// Optional C++ math operators +//------------------------------------------------------------------------------- + +// Vector2 operators +static constexpr Vector2 Vector2Zeros = { 0, 0 }; +static constexpr Vector2 Vector2Ones = { 1, 1 }; +static constexpr Vector2 Vector2UnitX = { 1, 0 }; +static constexpr Vector2 Vector2UnitY = { 0, 1 }; + +inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Add(lhs, rhs); +} + +inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Add(lhs, rhs); + return lhs; +} + +inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Subtract(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Subtract(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Multiply(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Multiply(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) +{ + return Vector2Transform(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) +{ + lhs = Vector2Transform(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, 1.0f/rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, 1.0f/rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Divide(lhs, rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector2& lhs, const Vector2& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); +} + +inline bool operator != (const Vector2& lhs, const Vector2& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); +} + +// Vector3 operators +static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; +static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; +static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; +static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; +static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; + +inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Add(lhs, rhs); +} + +inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Add(lhs, rhs); + return lhs; +} + +inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Subtract(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Subtract(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Multiply(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Multiply(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) +{ + return Vector3Transform(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) +{ + lhs = Vector3Transform(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, 1.0f/rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, 1.0f/rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Divide(lhs, rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector3& lhs, const Vector3& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); +} + +inline bool operator != (const Vector3& lhs, const Vector3& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); +} + +// Vector4 operators +static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; +static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; +static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; +static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; +static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; +static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; + +inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Add(lhs, rhs); +} + +inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Add(lhs, rhs); + return lhs; +} + +inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Subtract(lhs, rhs); +} + +inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Subtract(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Multiply(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Multiply(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, 1.0f/rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, 1.0f/rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Divide(lhs, rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector4& lhs, const Vector4& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); +} + +inline bool operator != (const Vector4& lhs, const Vector4& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); +} + +// Quaternion operators +static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; +static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; +static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; + +inline Quaternion operator + (const Quaternion& lhs, const float& rhs) +{ + return QuaternionAddValue(lhs, rhs); +} + +inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionAddValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator - (const Quaternion& lhs, const float& rhs) +{ + return QuaternionSubtractValue(lhs, rhs); +} + +inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionSubtractValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) +{ + return QuaternionTransform(lhs, rhs); +} + +inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) +{ + lhs = QuaternionTransform(lhs, rhs); + return lhs; +} + +// Matrix operators +inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixAdd(lhs, rhs); +} + +inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixAdd(lhs, rhs); + return lhs; +} + +inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixSubtract(lhs, rhs); +} + +inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixSubtract(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixMultiply(lhs, rhs); +} + +inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixMultiply(lhs, rhs); + return lhs; +} +//------------------------------------------------------------------------------- +#endif // C++ operators + #endif // RAYMATH_H diff --git a/include/rlgl.h b/include/rlgl.h index 6cf9c7e..c39d096 100644 --- a/include/rlgl.h +++ b/include/rlgl.h @@ -1,24 +1,24 @@ /********************************************************************************************** * -* rlgl v4.5 - A multi-OpenGL abstraction layer with an immediate-mode style API +* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API * * DESCRIPTION: -* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0) +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) * that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) * * ADDITIONAL NOTES: * When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are -* initialized on rlglInit() to accumulate vertex data. +* initialized on rlglInit() to accumulate vertex data * -* When an internal state change is required all the stored vertex data is renderer in batch, -* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch. +* When an internal state change is required all the stored vertex data is rendered in batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch * * Some resources are also loaded for convenience, here the complete list: * - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data * - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 * - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) * -* Internal buffer (and resources) must be manually unloaded calling rlglClose(). +* Internal buffer (and resources) must be manually unloaded calling rlglClose() * * CONFIGURATION: * #define GRAPHICS_API_OPENGL_11 @@ -32,9 +32,9 @@ * required by any other module, use rlGetVersion() to check it * * #define RLGL_IMPLEMENTATION -* Generates the implementation of the library into the included file. +* Generates the implementation of the library into the included file * If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. +* or source files without problems. But only ONE file should hold the implementation * * #define RLGL_RENDER_TEXTURES_HINT * Enable framebuffer objects (fbo) support (enabled by default) @@ -56,24 +56,27 @@ * * #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack * #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance -* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance +* #define RL_CULL_DISTANCE_NEAR 0.05 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 4000.0 // Default projection matrix far cull distance * * When loading a shader, the following vertex attributes and uniform * location names are tried to be set automatically: * -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: 0 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: 1 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: 2 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: 3 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: 4 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: 5 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS * #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) * #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices * #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) * #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) * #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) @@ -85,7 +88,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2023 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -107,16 +110,17 @@ #ifndef RLGL_H #define RLGL_H -#define RLGL_VERSION "4.5" +#define RLGL_VERSION "5.0" -// Function specifiers in case library is build/used as a shared library (Windows) +// Function specifiers in case library is build/used as a shared library // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -#if defined(_WIN32) - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) - #elif defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) - #endif +// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) +#elif defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) #endif // Function specifiers definition @@ -230,10 +234,10 @@ // Projection matrix culling #ifndef RL_CULL_DISTANCE_NEAR - #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance + #define RL_CULL_DISTANCE_NEAR 0.05 // Default near cull distance #endif #ifndef RL_CULL_DISTANCE_FAR - #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance + #define RL_CULL_DISTANCE_FAR 4000.0 // Default far cull distance #endif // Texture parameters (equivalent to OpenGL defines) @@ -318,6 +322,42 @@ #define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA #define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR +#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER +#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER + +// Default shader vertex attribute locations +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 +#endif + #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif +#ifdef RL_SUPPORT_MESH_GPU_SKINNING +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 +#endif +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCE_TX + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCE_TX 9 +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -346,6 +386,7 @@ typedef struct rlVertexBuffer { float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) @@ -354,7 +395,7 @@ typedef struct rlVertexBuffer { unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) #endif unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) + unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) } rlVertexBuffer; // Draw call type @@ -503,6 +544,10 @@ typedef enum { RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d } rlShaderUniformDataType; @@ -555,30 +600,33 @@ typedef enum { extern "C" { // Prevents name mangling of functions #endif -RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed -RLAPI void rlPushMatrix(void); // Push the current matrix to stack -RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack -RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix -RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix -RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix -RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix -RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area +RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances +RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near +RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far //------------------------------------------------------------------------------------ // Functions Declaration - Vertex level operations //------------------------------------------------------------------------------------ -RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) -RLAPI void rlEnd(void); // Finish vertex providing -RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int -RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float -RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float -RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float -RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float -RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte -RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float //------------------------------------------------------------------------------------ @@ -592,13 +640,13 @@ RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array ( RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) -RLAPI void rlEnableVertexBufferElement(unsigned int id);// Enable vertex buffer element (VBO element) +RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index -RLAPI void rlDisableVertexAttribute(unsigned int index);// Disable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index #if defined(GRAPHICS_API_OPENGL_11) -RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer -RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer +RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer #endif // Textures state @@ -617,11 +665,13 @@ RLAPI void rlDisableShader(void); // Disable shader progra // Framebuffer state RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer +RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer +RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) // General render state -RLAPI void rlEnableColorBlend(void); // Enable color blending +RLAPI void rlEnableColorBlend(void); // Enable color blending RLAPI void rlDisableColorBlend(void); // Disable color blending RLAPI void rlEnableDepthTest(void); // Enable depth test RLAPI void rlDisableDepthTest(void); // Disable depth test @@ -629,13 +679,15 @@ RLAPI void rlEnableDepthMask(void); // Enable depth write RLAPI void rlDisableDepthMask(void); // Disable depth write RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control RLAPI void rlSetCullFace(int mode); // Set face culling mode RLAPI void rlEnableScissorTest(void); // Enable scissor test RLAPI void rlDisableScissorTest(void); // Disable scissor test RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnablePointMode(void); // Enable point mode +RLAPI void rlDisablePointMode(void); // Disable point mode RLAPI void rlEnableWireMode(void); // Enable wire mode -RLAPI void rlEnablePointMode(void); // Enable point mode -RLAPI void rlDisableWireMode(void); // Disable wire mode ( and point ) maybe rename +RLAPI void rlDisableWireMode(void); // Disable wire mode RLAPI void rlSetLineWidth(float width); // Set the line drawing width RLAPI float rlGetLineWidth(void); // Get the line drawing width RLAPI void rlEnableSmoothLines(void); // Enable line aliasing @@ -671,48 +723,48 @@ RLAPI int *rlGetShaderLocsDefault(void); // Get default shader lo // Render batch management // NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode // but this render batch API is exposed in case of custom batches are required -RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system -RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system -RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) -RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) -RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch -RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex +RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits //------------------------------------------------------------------------------------------------------------------------ // Vertex buffers management -RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported -RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer attribute -RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load a new attributes element buffer -RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update GPU buffer with new data -RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements with new data -RLAPI void rlUnloadVertexArray(unsigned int vaoId); -RLAPI void rlUnloadVertexBuffer(unsigned int vboId); -RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, const void *pointer); -RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); -RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value -RLAPI void rlDrawVertexArray(int offset, int count); -RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); -RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); -RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object +RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer +RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer +RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided +RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) +RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing // Textures management -RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU -RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) -RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format); // Load texture cubemap -RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update GPU texture with new data -RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture -RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data +RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) // Framebuffer management (fbo) -RLAPI unsigned int rlLoadFramebuffer(int width, int height); // Load an empty framebuffer -RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU @@ -723,14 +775,15 @@ RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fSha RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute -RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) // Compute shader management RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program -RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) +RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) // Shader buffer storage object management (ssbo) RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) @@ -773,6 +826,12 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #if defined(RLGL_IMPLEMENTATION) +// Expose OpenGL functions from glad in raylib +#if defined(BUILD_LIBTYPE_SHARED) + #define GLAD_API_CALL_EXPORT + #define GLAD_API_CALL_EXPORT_BUILD +#endif + #if defined(GRAPHICS_API_OPENGL_11) #if defined(__APPLE__) #include // OpenGL 1.1 library for OSX @@ -808,9 +867,9 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #define GL_GLEXT_PROTOTYPES #include // OpenGL ES 2.0 extensions library #elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 can be enabled on PLATFORM_DESKTOP, + // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_DESKTOP_SDL) + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) #define GLAD_GLES2_IMPLEMENTATION #include "external/glad_gles2.h" #else @@ -891,6 +950,14 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE #endif +#ifndef GL_PROGRAM_POINT_SIZE + #define GL_PROGRAM_POINT_SIZE 0x8642 +#endif + +#ifndef GL_LINE_WIDTH + #define GL_LINE_WIDTH 0x0B21 +#endif + #if defined(GRAPHICS_API_OPENGL_11) #define GL_UNSIGNED_SHORT_5_6_5 0x8363 #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 @@ -912,22 +979,31 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad // Default shader vertex attribute names to set location points #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: 0 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: 1 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: 2 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: 3 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: 4 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: 5 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCE_TX + #define RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCE_TX "instanceTransform" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCE_TX #endif #ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP @@ -948,6 +1024,9 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) #endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES + #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +#endif #ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) #endif @@ -1042,6 +1121,9 @@ typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- +static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; +static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) static rlglData RLGL = { 0 }; #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 @@ -1072,8 +1154,15 @@ static const char *rlGetCompressedFormatName(int format); // Get compressed form static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) // Auxiliar matrix math functions +typedef struct rl_float16 { + float v[16]; +} rl_float16; +static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data +#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix static Matrix rlMatrixIdentity(void); // Get identity matrix static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices +static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix +static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix //---------------------------------------------------------------------------------- // Module Functions Definition - Matrix operations @@ -1242,7 +1331,7 @@ void rlMultMatrixf(const float *matf) matf[2], matf[6], matf[10], matf[14], matf[3], matf[7], matf[11], matf[15] }; - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, mat); + *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); } // Multiply the current matrix by a perspective matrix generated by parameters @@ -1316,6 +1405,25 @@ void rlViewport(int x, int y, int width, int height) glViewport(x, y, width, height); } +// Set clip planes distances +void rlSetClipPlanes(double nearPlane, double farPlane) +{ + rlCullDistanceNear = nearPlane; + rlCullDistanceFar = farPlane; +} + +// Get cull plane distance near +double rlGetCullDistanceNear(void) +{ + return rlCullDistanceNear; +} + +// Get cull plane distance far +double rlGetCullDistanceFar(void) +{ + return rlCullDistanceFar; +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Vertex level operations //---------------------------------------------------------------------------------- @@ -1333,7 +1441,7 @@ void rlBegin(int mode) } } -void rlEnd() { glEnd(); } +void rlEnd(void) { glEnd(); } void rlVertex2i(int x, int y) { glVertex2i(x, y); } void rlVertex2f(float x, float y) { glVertex2f(x, y); } void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } @@ -1402,8 +1510,8 @@ void rlVertex3f(float x, float y, float z) tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; } - // WARNING: We can't break primitives when launching a new batch. - // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices. + // WARNING: We can't break primitives when launching a new batch + // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) { @@ -1436,7 +1544,10 @@ void rlVertex3f(float x, float y, float z) RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; - // WARNING: By default rlVertexBuffer struct does not store normals + // Add current normal + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; // Add current color RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; @@ -1472,9 +1583,26 @@ void rlTexCoord2f(float x, float y) // NOTE: Normals limited to TRIANGLES only? void rlNormal3f(float x, float y, float z) { - RLGL.State.normalx = x; - RLGL.State.normaly = y; - RLGL.State.normalz = z; + float normalx = x; + float normaly = y; + float normalz = z; + if (RLGL.State.transformRequired) + { + normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; + normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; + normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; + } + float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); + if (length != 0.0f) + { + float ilength = 1.0f/length; + normalx *= ilength; + normaly *= ilength; + normalz *= ilength; + } + RLGL.State.normalx = normalx; + RLGL.State.normaly = normaly; + RLGL.State.normalz = normalz; } // Define one vertex (color) @@ -1620,7 +1748,6 @@ void rlTextureParameters(unsigned int id, int param, int value) #endif } else glTexParameteri(GL_TEXTURE_2D, param, value); - } break; case RL_TEXTURE_MAG_FILTER: case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; @@ -1665,7 +1792,6 @@ void rlCubemapParameters(unsigned int id, int param, int value) else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); } else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - } break; case RL_TEXTURE_MAG_FILTER: case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; @@ -1713,6 +1839,16 @@ void rlEnableFramebuffer(unsigned int id) #endif } +// return the active render texture (fbo) +unsigned int rlGetActiveFramebuffer(void) +{ + GLint fboId = 0; +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); +#endif + return fboId; +} + // Disable rendering to texture void rlDisableFramebuffer(void) { @@ -1729,6 +1865,14 @@ void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX #endif } +// Bind framebuffer object (fbo) +void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(target, framebuffer); +#endif +} + // Activate multiple draw color buffers // NOTE: One color buffer is always active by default void rlActiveDrawBuffers(int count) @@ -1745,16 +1889,6 @@ void rlActiveDrawBuffers(int count) else { unsigned int buffers[8] = { -#if defined(GRAPHICS_API_OPENGL_ES3) - GL_COLOR_ATTACHMENT0_EXT, - GL_COLOR_ATTACHMENT1_EXT, - GL_COLOR_ATTACHMENT2_EXT, - GL_COLOR_ATTACHMENT3_EXT, - GL_COLOR_ATTACHMENT4_EXT, - GL_COLOR_ATTACHMENT5_EXT, - GL_COLOR_ATTACHMENT6_EXT, - GL_COLOR_ATTACHMENT7_EXT, -#else GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, @@ -1763,14 +1897,9 @@ void rlActiveDrawBuffers(int count) GL_COLOR_ATTACHMENT5, GL_COLOR_ATTACHMENT6, GL_COLOR_ATTACHMENT7, -#endif }; -#if defined(GRAPHICS_API_OPENGL_ES3) - glDrawBuffersEXT(count, buffers); -#else glDrawBuffers(count, buffers); -#endif } } else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); @@ -1805,6 +1934,9 @@ void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } // Disable backface culling void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } +// Set color mask active for screen read/draw +void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } + // Set face culling mode void rlSetCullFace(int mode) { @@ -1834,6 +1966,16 @@ void rlEnableWireMode(void) #endif } +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Enable point mode void rlEnablePointMode(void) { #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) @@ -1842,8 +1984,9 @@ void rlEnablePointMode(void) glEnable(GL_PROGRAM_POINT_SIZE); #endif } -// Disable wire mode -void rlDisableWireMode(void) + +// Disable point mode +void rlDisablePointMode(void) { #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) // NOTE: glPolygonMode() not available on OpenGL ES @@ -1924,7 +2067,7 @@ void rlClearScreenBuffers(void) } // Check and log OpenGL error codes -void rlCheckErrors() +void rlCheckErrors(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) int check = 1; @@ -1967,14 +2110,12 @@ void rlSetBlendMode(int mode) { // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); - } break; case RL_BLEND_CUSTOM_SEPARATE: { // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); - } break; default: break; } @@ -2122,7 +2263,10 @@ void rlglInit(int width, int height) RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; // Init default vertex arrays buffers + // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; RLGL.currentBatch = &RLGL.defaultBatch; // Init stack matrices (emulating OpenGL 1.1) @@ -2273,7 +2417,7 @@ void rlLoadExtensions(void *loader) #elif defined(GRAPHICS_API_OPENGL_ES2) - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_DESKTOP_SDL) + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) // TODO: Support GLAD loader for OpenGL ES 3.0 if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); @@ -2281,7 +2425,7 @@ void rlLoadExtensions(void *loader) // Get supported extensions list GLint numExt = 0; - const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char **extList = (const char **)RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string // NOTE: We have to duplicate string because glGetString() returns a const string @@ -2325,25 +2469,47 @@ void rlLoadExtensions(void *loader) } // Check instanced rendering support - if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // Web ANGLE + if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); - - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - else - { - if ((strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) && // Standard EXT - (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0)) + // Specific check + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + } + else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT { glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); - - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; } + else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); + } + + // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) + { + // GL_ANGLE_draw_instanced doesn't exist + if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + } + else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + } + + // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; } // Check NPOT textures support @@ -2356,11 +2522,11 @@ void rlLoadExtensions(void *loader) // Check depth texture support if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; - if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format + if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; - if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL - if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL // Check texture compression support: DXT if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || @@ -2567,6 +2733,7 @@ rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad #if defined(GRAPHICS_API_OPENGL_33) batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) @@ -2577,6 +2744,7 @@ rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; int k = 0; @@ -2626,16 +2794,23 @@ rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - // Vertex color buffer (shader-location = 3) + // Vertex normal buffer (shader-location = 2) glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); // Fill index buffer - glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); + glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); #if defined(GRAPHICS_API_OPENGL_33) glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); #endif @@ -2690,10 +2865,10 @@ void rlUnloadRenderBatch(rlRenderBatch batch) if (RLGL.ExtSupported.vao) { glBindVertexArray(batch.vertexBuffer[i].vaoId); - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - glDisableVertexAttribArray(3); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); glBindVertexArray(0); } @@ -2702,6 +2877,7 @@ void rlUnloadRenderBatch(rlRenderBatch batch) glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); // Delete VAOs from GPU (VRAM) if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); @@ -2709,6 +2885,7 @@ void rlUnloadRenderBatch(rlRenderBatch batch) // Free vertex arrays memory from CPU (RAM) RL_FREE(batch.vertexBuffer[i].vertices); RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].normals); RL_FREE(batch.vertexBuffer[i].colors); RL_FREE(batch.vertexBuffer[i].indices); } @@ -2743,16 +2920,21 @@ void rlDrawRenderBatch(rlRenderBatch *batch) glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer - // Colors buffer + // Normals buffer glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer - // NOTE: glMapBuffer() causes sync issue. - // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job. - // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer(). + // NOTE: glMapBuffer() causes sync issue + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new - // allocated pointer immediately even if GPU is still working with the previous data. + // allocated pointer immediately even if GPU is still working with the previous data // Another option: map the buffer object into client's memory // Probably this code could be moved somewhere else... @@ -2797,13 +2979,30 @@ void rlDrawRenderBatch(rlRenderBatch *batch) // Create modelview-projection matrix and upload to shader Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); - float matMVPfloat[16] = { - matMVP.m0, matMVP.m1, matMVP.m2, matMVP.m3, - matMVP.m4, matMVP.m5, matMVP.m6, matMVP.m7, - matMVP.m8, matMVP.m9, matMVP.m10, matMVP.m11, - matMVP.m12, matMVP.m13, matMVP.m14, matMVP.m15 - }; - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, matMVPfloat); + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); + } + + // WARNING: For the following setup of the view, model, and normal matrices, it is expected that + // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); + } if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); else @@ -2818,12 +3017,17 @@ void rlDrawRenderBatch(rlRenderBatch *batch) glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - // Bind vertex attrib: color (shader-location = 3) + // Bind vertex attrib: normal (shader-location = 2) glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); } // Setup some default shader values @@ -2847,21 +3051,21 @@ void rlDrawRenderBatch(rlRenderBatch *batch) for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) { - // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default + // Bind current draw call texture, activated as GL_TEXTURE0 and bound to sampler2D texture0 by default glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); else { -#if defined(GRAPHICS_API_OPENGL_33) + #if defined(GRAPHICS_API_OPENGL_33) // We need to define the number of indices to be processed: elementCount*6 // NOTE: The final parameter tells the GPU the offset in bytes from the // start of the index buffer to the location of the first index to process glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); -#endif + #endif } vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); @@ -3103,6 +3307,7 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, // Activate Trilinear filtering if mipmaps are available glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount); // Required for user-defined mip count } #endif @@ -3179,11 +3384,17 @@ unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) // Load texture cubemap // NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), // expected the following convention: +X, -X, +Y, -Y, +Z, -Z -unsigned int rlLoadTextureCubemap(const void *data, int size, int format) +unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) { unsigned int id = 0; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int mipSize = size; + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + unsigned int dataSize = rlGetPixelDataSize(size, size, format); glGenTextures(1, &id); @@ -3194,24 +3405,28 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format) if (glInternalFormat != 0) { - // Load cubemap faces - for (unsigned int i = 0; i < 6; i++) + // Load cubemap faces/mipmaps + for (int i = 0; i < 6*mipmapCount; i++) { + int mipmapLevel = i/6; + int face = i%6; + if (data == NULL) { if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) { - if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) - || (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) - TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); - else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, NULL); + if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); } else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); } else { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); - else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); } #if defined(GRAPHICS_API_OPENGL_33) @@ -3230,11 +3445,23 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format) glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } #endif + if (face == 5) + { + mipSize /= 2; + if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipSize < 1) mipSize = 1; + + dataSize = rlGetPixelDataSize(mipSize, mipSize, format); + } } } // Set cubemap texture sampling parameters - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); @@ -3378,7 +3605,6 @@ void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int #endif } - // Read texture pixel data void *rlReadTexturePixels(unsigned int id, int width, int height, int format) { @@ -3394,8 +3620,8 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); - // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding. - // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) glPixelStorei(GL_PACK_ALIGNMENT, 1); @@ -3416,13 +3642,13 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) #if defined(GRAPHICS_API_OPENGL_ES2) // glGetTexImage() is not available on OpenGL ES 2.0 - // Texture width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. + // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id // Two possible Options: // 1 - Bind texture to color fbo attachment and glReadPixels() // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() // We are using Option 1, just need to care for texture format on retrieval // NOTE: This behaviour could be conditioned by graphic driver... - unsigned int fboId = rlLoadFramebuffer(width, height); + unsigned int fboId = rlLoadFramebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, fboId); glBindTexture(GL_TEXTURE_2D, 0); @@ -3446,29 +3672,37 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) // Read screen pixel data (color buffer) unsigned char *rlReadScreenPixels(int width, int height) { - unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + unsigned char *imgData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, imgData); // Flip image vertically! - unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); - - for (int y = height - 1; y >= 0; y--) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + for (int y = height - 1; y >= height/2; y--) { - for (int x = 0; x < (width*4); x++) + for (int x = 0; x < (width*4); x += 4) { - imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + unsigned int s = ((height - 1) - y)*width*4 + x; + unsigned int e = y*width*4 + x; - // Set alpha component value to 255 (no trasparent image retrieval) - // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! - if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + unsigned char r = imgData[s]; + unsigned char g = imgData[s+1]; + unsigned char b = imgData[s+2]; + + imgData[s] = imgData[e]; + imgData[s+1] = imgData[e+1]; + imgData[s+2] = imgData[e+2]; + imgData[s+3] = 255; // Set alpha component value to 255 (no trasparent image retrieval) + + imgData[e] = r; + imgData[e+1] = g; + imgData[e+2] = b; + imgData[e+3] = 255; // Ditto } } - RL_FREE(screenData); - return imgData; // NOTE: image data should be freed } @@ -3476,7 +3710,7 @@ unsigned char *rlReadScreenPixels(int width, int height) //----------------------------------------------------------------------------------------- // Load a framebuffer to be used for rendering // NOTE: No textures attached -unsigned int rlLoadFramebuffer(int width, int height) +unsigned int rlLoadFramebuffer(void) { unsigned int fboId = 0; @@ -3509,19 +3743,16 @@ void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); - } break; case RL_ATTACHMENT_DEPTH: { if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); - } break; case RL_ATTACHMENT_STENCIL: { if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); - } break; default: break; } @@ -3582,7 +3813,7 @@ void rlUnloadFramebuffer(unsigned int id) else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, - // the texture image is automatically detached from the currently bound framebuffer. + // the texture image is automatically detached from the currently bound framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &id); @@ -3731,7 +3962,7 @@ void rlDrawVertexArrayElements(int offset, int count, const void *buffer) void rlDrawVertexArrayInstanced(int offset, int count, int instances) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); + glDrawArraysInstanced(GL_TRIANGLES, offset, count, instances); #endif } @@ -3784,10 +4015,16 @@ unsigned int rlLoadVertexArray(void) } // Set vertex attribute -void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, const void *pointer) +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glVertexAttribPointer(index, compSize, type, normalized, stride, pointer); + // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT + // Additional types (depends on OpenGL version or extensions): + // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, + // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV + + size_t offsetNative = offset; + glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); #endif } @@ -3834,18 +4071,18 @@ unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) unsigned int fragmentShaderId = 0; // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - // In case no vertex shader was provided or compilation failed, we use default vertex shader - if (vertexShaderId == 0) vertexShaderId = RLGL.State.defaultVShaderId; + else vertexShaderId = RLGL.State.defaultVShaderId; // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - // In case no fragment shader was provided or compilation failed, we use default fragment shader - if (fragmentShaderId == 0) fragmentShaderId = RLGL.State.defaultFShaderId; + else fragmentShaderId = RLGL.State.defaultFShaderId; // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; - else + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) { // One of or both shader are new, we need to compile a new shader program id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); @@ -3923,6 +4160,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; } @@ -3938,6 +4177,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); RL_FREE(log); } + + shader = 0; } else { @@ -3948,6 +4189,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; } @@ -3970,12 +4213,18 @@ unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) glAttachShader(program, fShaderId); // NOTE: Default attribute shader locations must be Bound before linking - glBindAttribLocation(program, 0, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - glBindAttribLocation(program, 1, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - glBindAttribLocation(program, 2, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); - glBindAttribLocation(program, 3, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - glBindAttribLocation(program, 4, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); - glBindAttribLocation(program, 5, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCE_TX, RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCE_TX); + +#ifdef RL_SUPPORT_MESH_GPU_SKINNING + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); +#endif // NOTE: If some attrib name is no found on the shader, it locations becomes -1 @@ -4008,7 +4257,7 @@ unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) else { // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero. + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero //GLint binarySize = 0; //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); @@ -4068,8 +4317,16 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() } #endif } @@ -4103,12 +4360,31 @@ void rlSetUniformMatrix(int locIndex, Matrix mat) #endif } +// Set shader value uniform matrix +void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) + glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); +#elif defined(GRAPHICS_API_OPENGL_ES2) + // WARNING: WebGL does not support Matrix transpose ("true" parameter) + // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); +#endif +} + // Set shader value uniform sampler void rlSetUniformSampler(int locIndex, unsigned int textureId) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Check if texture is already active - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) if (RLGL.State.activeTextureId[i] == textureId) return; + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == textureId) + { + glUniform1i(locIndex, 1 + i); + return; + } + } // Register a new active texture for the internal batch system // NOTE: Default texture is always activated as GL_TEXTURE0 @@ -4175,12 +4451,14 @@ unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) else { // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero. + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero //GLint binarySize = 0; //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); #endif return program; @@ -4205,6 +4483,8 @@ unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHi glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); #endif return ssbo; @@ -4215,7 +4495,10 @@ void rlUnloadShaderBuffer(unsigned int ssboId) { #if defined(GRAPHICS_API_OPENGL_43) glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); #endif + } // Update SSBO buffer data @@ -4230,14 +4513,14 @@ void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSi // Get SSBO buffer size unsigned int rlGetShaderBufferSize(unsigned int id) { - long long size = 0; - #if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetInteger64v(GL_SHADER_STORAGE_BUFFER_SIZE, &size); -#endif - + glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); return (size > 0)? (unsigned int)size : 0; +#else + return 0; +#endif } // Read SSBO buffer data (GPU->CPU) @@ -4275,6 +4558,8 @@ void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool re rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); #endif } @@ -4353,7 +4638,7 @@ Matrix rlGetMatrixTransform(void) } // Get internal projection matrix for stereo render (selected eye) -RLAPI Matrix rlGetMatrixProjectionStereo(int eye) +Matrix rlGetMatrixProjectionStereo(int eye) { Matrix mat = rlMatrixIdentity(); #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -4363,7 +4648,7 @@ RLAPI Matrix rlGetMatrixProjectionStereo(int eye) } // Get internal view offset matrix for stereo render (selected eye) -RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye) +Matrix rlGetMatrixViewOffsetStereo(int eye) { Matrix mat = rlMatrixIdentity(); #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -4431,10 +4716,10 @@ void rlLoadDrawQuad(void) glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); // Bind vertex attributes (position, texcoords) - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords // Draw quad glBindVertexArray(quadVAO); @@ -4505,12 +4790,12 @@ void rlLoadDrawCube(void) // Bind vertex attributes (position, normals, texcoords) glBindVertexArray(cubeVAO); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); @@ -4589,7 +4874,16 @@ static void rlLoadShaderDefault(void) "out vec2 fragTexCoord; \n" "out vec4 fragColor; \n" #endif -#if defined(GRAPHICS_API_OPENGL_ES2) + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) "#version 100 \n" "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) "attribute vec3 vertexPosition; \n" @@ -4598,6 +4892,7 @@ static void rlLoadShaderDefault(void) "varying vec2 fragTexCoord; \n" "varying vec4 fragColor; \n" #endif + "uniform mat4 mvp; \n" "void main() \n" "{ \n" @@ -4632,7 +4927,21 @@ static void rlLoadShaderDefault(void) " finalColor = texelColor*colDiffuse*fragColor; \n" "} \n"; #endif -#if defined(GRAPHICS_API_OPENGL_ES2) + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_ES2) "#version 100 \n" "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) "varying vec2 fragTexCoord; \n" @@ -4658,14 +4967,14 @@ static void rlLoadShaderDefault(void) TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); // Set default shader locations: attributes locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, "vertexPosition"); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, "vertexTexCoord"); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, "vertexColor"); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); // Set default shader locations: uniform locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, "mvp"); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, "colDiffuse"); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, "texture0"); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); } else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); } @@ -4802,7 +5111,8 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - dataSize = width*height*bpp/8; // Total data size in bytes + double bytesPerPixel = (double)bpp/8.0; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 @@ -4817,6 +5127,31 @@ static int rlGetPixelDataSize(int width, int height, int format) // Auxiliar math functions +// Get float array of matrix data +static rl_float16 rlMatrixToFloatV(Matrix mat) +{ + rl_float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + // Get identity matrix static Matrix rlMatrixIdentity(void) { @@ -4856,4 +5191,76 @@ static Matrix rlMatrixMultiply(Matrix left, Matrix right) return result; } +// Transposes provided matrix +static Matrix rlMatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +static Matrix rlMatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + #endif // RLGL_IMPLEMENTATION diff --git a/lib/libraylibdll.a b/lib/libraylibdll.a index 3665ba9..a5ba5ad 100644 Binary files a/lib/libraylibdll.a and b/lib/libraylibdll.a differ diff --git a/lib/libremidi.lib b/lib/libremidi.lib index aa21c4f..606a4c5 100644 Binary files a/lib/libremidi.lib and b/lib/libremidi.lib differ