ladybird/Userland/Applications/Piano/KeysWidget.cpp
Andreas Kling 5d180d1f99 Everywhere: Rename ASSERT => VERIFY
(...and ASSERT_NOT_REACHED => VERIFY_NOT_REACHED)

Since all of these checks are done in release builds as well,
let's rename them to VERIFY to prevent confusion, as everyone is
used to assertions being compiled out in release.

We can introduce a new ASSERT macro that is specifically for debug
checks, but I'm doing this wholesale conversion first since we've
accumulated thousands of these already, and it's not immediately
obvious which ones are suitable for ASSERT.
2021-02-23 20:56:54 +01:00

328 lines
8.3 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "KeysWidget.h"
#include "TrackManager.h"
#include <LibGUI/Painter.h>
KeysWidget::KeysWidget(TrackManager& track_manager)
: m_track_manager(track_manager)
{
set_fill_with_background_color(true);
}
KeysWidget::~KeysWidget()
{
}
int KeysWidget::mouse_note() const
{
if (m_mouse_down && m_mouse_note + m_track_manager.octave_base() < note_count)
return m_mouse_note; // Can be -1.
else
return -1;
}
void KeysWidget::set_key(int key, Switch switch_key)
{
if (key == -1 || key + m_track_manager.octave_base() >= note_count)
return;
if (switch_key == On) {
++m_key_on[key];
} else {
if (m_key_on[key] >= 1)
--m_key_on[key];
}
VERIFY(m_key_on[key] <= 2);
m_track_manager.set_note_current_octave(key, switch_key);
}
bool KeysWidget::note_is_set(int note) const
{
if (note < m_track_manager.octave_base())
return false;
if (note >= m_track_manager.octave_base() + note_count)
return false;
return m_key_on[note - m_track_manager.octave_base()] != 0;
}
int KeysWidget::key_code_to_key(int key_code) const
{
switch (key_code) {
case Key_A:
return 0;
case Key_W:
return 1;
case Key_S:
return 2;
case Key_E:
return 3;
case Key_D:
return 4;
case Key_F:
return 5;
case Key_T:
return 6;
case Key_G:
return 7;
case Key_Y:
return 8;
case Key_H:
return 9;
case Key_U:
return 10;
case Key_J:
return 11;
case Key_K:
return 12;
case Key_O:
return 13;
case Key_L:
return 14;
case Key_P:
return 15;
case Key_Semicolon:
return 16;
case Key_Apostrophe:
return 17;
case Key_RightBracket:
return 18;
case Key_Return:
return 19;
default:
return -1;
}
}
constexpr int white_key_width = 24;
constexpr int black_key_width = 16;
constexpr int black_key_x_offset = black_key_width / 2;
constexpr int black_key_height = 60;
constexpr char white_key_labels[] = {
'A',
'S',
'D',
'F',
'G',
'H',
'J',
'K',
'L',
';',
'\'',
'r',
};
constexpr int white_key_labels_count = sizeof(white_key_labels) / sizeof(char);
constexpr char black_key_labels[] = {
'W',
'E',
'T',
'Y',
'U',
'O',
'P',
']',
};
constexpr int black_key_labels_count = sizeof(black_key_labels) / sizeof(char);
constexpr int black_key_offsets[] = {
white_key_width,
white_key_width * 2,
white_key_width,
white_key_width,
white_key_width * 2,
};
constexpr int white_key_note_accumulator[] = {
2,
2,
1,
2,
2,
2,
1,
};
constexpr int black_key_note_accumulator[] = {
2,
3,
2,
2,
3,
};
void KeysWidget::paint_event(GUI::PaintEvent& event)
{
GUI::Painter painter(*this);
painter.translate(frame_thickness(), frame_thickness());
int note = 0;
int x = 0;
int i = 0;
for (;;) {
Gfx::IntRect rect(x, 0, white_key_width, frame_inner_rect().height());
painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::White);
painter.draw_rect(rect, Color::Black);
if (i < white_key_labels_count) {
rect.set_height(rect.height() * 1.5);
painter.draw_text(rect, StringView(&white_key_labels[i], 1), Gfx::TextAlignment::Center, Color::Black);
}
note += white_key_note_accumulator[i % white_keys_per_octave];
x += white_key_width;
++i;
if (note + m_track_manager.octave_base() >= note_count)
break;
if (x >= frame_inner_rect().width())
break;
}
note = 1;
x = white_key_width - black_key_x_offset;
i = 0;
for (;;) {
Gfx::IntRect rect(x, 0, black_key_width, black_key_height);
painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::Black);
painter.draw_rect(rect, Color::Black);
if (i < black_key_labels_count) {
rect.set_height(rect.height() * 1.5);
painter.draw_text(rect, StringView(&black_key_labels[i], 1), Gfx::TextAlignment::Center, Color::White);
}
note += black_key_note_accumulator[i % black_keys_per_octave];
x += black_key_offsets[i % black_keys_per_octave];
++i;
if (note + m_track_manager.octave_base() >= note_count)
break;
if (x >= frame_inner_rect().width())
break;
}
GUI::Frame::paint_event(event);
}
constexpr int notes_per_white_key[] = {
1,
3,
5,
6,
8,
10,
12,
};
// Keep in mind that in any of these functions a note value can be out of
// bounds. Bounds checking is done in set_key().
static inline int note_from_white_keys(int white_keys)
{
int octaves = white_keys / white_keys_per_octave;
int remainder = white_keys % white_keys_per_octave;
int notes_from_octaves = octaves * notes_per_octave;
int notes_from_remainder = notes_per_white_key[remainder];
int note = (notes_from_octaves + notes_from_remainder) - 1;
return note;
}
int KeysWidget::note_for_event_position(const Gfx::IntPoint& a_point) const
{
if (!frame_inner_rect().contains(a_point))
return -1;
auto point = a_point;
point.move_by(-frame_thickness(), -frame_thickness());
int white_keys = point.x() / white_key_width;
int note = note_from_white_keys(white_keys);
bool black_key_on_left = note != 0 && key_pattern[(note - 1) % notes_per_octave] == Black;
if (black_key_on_left) {
int black_key_x = (white_keys * white_key_width) - black_key_x_offset;
Gfx::IntRect black_key(black_key_x, 0, black_key_width, black_key_height);
if (black_key.contains(point))
return note - 1;
}
bool black_key_on_right = key_pattern[(note + 1) % notes_per_octave] == Black;
if (black_key_on_right) {
int black_key_x = ((white_keys + 1) * white_key_width) - black_key_x_offset;
Gfx::IntRect black_key(black_key_x, 0, black_key_width, black_key_height);
if (black_key.contains(point))
return note + 1;
}
return note;
}
void KeysWidget::mousedown_event(GUI::MouseEvent& event)
{
if (event.button() != GUI::MouseButton::Left)
return;
m_mouse_down = true;
m_mouse_note = note_for_event_position(event.position());
set_key(m_mouse_note, On);
update();
}
void KeysWidget::mouseup_event(GUI::MouseEvent& event)
{
if (event.button() != GUI::MouseButton::Left)
return;
m_mouse_down = false;
set_key(m_mouse_note, Off);
update();
}
void KeysWidget::mousemove_event(GUI::MouseEvent& event)
{
if (!m_mouse_down)
return;
int new_mouse_note = note_for_event_position(event.position());
if (m_mouse_note == new_mouse_note)
return;
set_key(m_mouse_note, Off);
set_key(new_mouse_note, On);
update();
m_mouse_note = new_mouse_note;
}