Skip to content

Commit

Permalink
Merge pull request #89 from juleskers/theme-cleanup
Browse files Browse the repository at this point in the history
Enum-ify `Theme`.
  • Loading branch information
pserwylo authored Jan 28, 2024
2 parents c0a1d54 + 252fac1 commit d17f656
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 100 deletions.
27 changes: 19 additions & 8 deletions app/src/main/java/com/nicobrailo/pianoli/Piano.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,32 +165,43 @@ int pos_to_key_idx(float pos_x, float pos_y) {
if (pos_y > keys_flats_height) return big_key_idx;

// Check if press is inside rect of flat key
Key flat = get_area_for_flat_key(big_key_idx);
Key flat = getAreaForSmallKey(big_key_idx + 1);
if (flat.contains(pos_x, pos_y)) return big_key_idx + 1;

if (big_key_idx > 0) {
Key prev_flat = get_area_for_flat_key(big_key_idx - 2);
Key prev_flat = getAreaForSmallKey(big_key_idx - 1);
if (prev_flat.contains(pos_x, pos_y)) return big_key_idx - 1;
}

// If not in the current or previous flat, it must be a hit in the big key
return big_key_idx;
}

Key get_area_for_key(int key_idx) {
int x_i = key_idx / 2 * keys_width;
@NonNull
Key getAreaForKey(int keyIdx) {
if ((keyIdx & 1) == 0) { // even positions are the full, big keys
return getAreaForBigKey(keyIdx);
} else {
return getAreaForSmallKey(keyIdx); // odd positions are the small/black/flat keys.
}
}

@NonNull
private Key getAreaForBigKey(int keyIdx) {
int x_i = keyIdx / 2 * keys_width;
return new Key(x_i, x_i + keys_width, 0, keys_height);
}

Key get_area_for_flat_key(int key_idx) {
final int octave_idx = (key_idx / 2) % 7;
if (octave_idx == 2 || octave_idx == 6) {
@NonNull
private Key getAreaForSmallKey(int keyIdx) {
final int octaveIdx = (keyIdx / 2) % 7;
if (octaveIdx == 2 || octaveIdx == 6) {
// Keys without flat get a null-area
return Key.CANT_TOUCH_THIS;
}

final int offset = keys_width - (keys_flat_width / 2);
int x_i = (key_idx / 2) * keys_width + offset;
int x_i = (keyIdx / 2) * keys_width + offset;
return new Key(x_i, x_i + keys_flat_width, 0, keys_flats_height);
}
}
48 changes: 24 additions & 24 deletions app/src/main/java/com/nicobrailo/pianoli/PianoCanvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ public PianoCanvas(Context context, AttributeSet as, int defStyle) {
public void reInitPiano(Context context, String prefSoundset) {
Log.i("PianOli::PianoCanvas", "re-initialising Piano");
this.piano = new Piano(screen_size_x, screen_size_y);
this.theme = Theme.fromPreferences(context);

String prefTheme = Preferences.selectedTheme(context);
this.theme = Theme.fromPreference(prefTheme);

// for config trigger updates
piano.addListener(appConfigTrigger);
Expand Down Expand Up @@ -142,24 +144,6 @@ private static void resetCanvas(Canvas canvas) {
canvas.drawPaint(p);
}

private void drawBigKeys(Canvas canvas) {
for (int i = 0; i < piano.get_keys_count(); i += 2) {
Paint paint = new Paint();
paint.setColor(theme.getColorForKey(i, piano.is_key_pressed(i)));
draw_key(canvas, piano.get_area_for_key(i), paint);
}
}

private void drawSmallKeys(Canvas canvas) {
for (int i = 1; i < piano.get_keys_count(); i += 2) {
Paint paint = new Paint();
paint.setColor(piano.is_key_pressed(i) ? Color.GRAY : 0xFF333333);
if (piano.get_area_for_flat_key(i) != null) {
draw_key(canvas, piano.get_area_for_flat_key(i), paint);
}
}
}

/**
* Overlays gear icons onto the currently-held and next expected flat keys.
*/
Expand All @@ -175,7 +159,15 @@ void drawConfigGears(Canvas androidCanvas) {
draw_icon_on_black_key(androidCanvas, gearIcon, appConfigTrigger.getNextExpectedKey(), normalSize, normalSize);
}

void draw_key(final Canvas canvas, final Key rect, final Paint p) {
void drawKey(final Canvas canvas, final int i) {
Key rect = piano.getAreaForKey(i);
if (rect == Key.CANT_TOUCH_THIS) {
return; // don't waste performance drawing the skipped black keys.
}

Paint p = new Paint();
p.setColor(theme.getColorForKey(i, piano.is_key_pressed(i)));

// Draw the main (solid) background of the key.

Rect r = new Rect();
Expand Down Expand Up @@ -252,7 +244,7 @@ void draw_key(final Canvas canvas, final Key rect, final Paint p) {
*/
void draw_icon_on_black_key(final Canvas canvas, final Drawable icon, int key_idx,
final int icon_width, final int icon_height) {
final Key key = piano.get_area_for_flat_key(key_idx);
final Key key = piano.getAreaForKey(key_idx);
int icon_x = ((key.x_f - key.x_i) / 2) + key.x_i;
int icon_y = icon_height;

Expand Down Expand Up @@ -284,9 +276,17 @@ public void redraw(SurfaceHolder surfaceHolder) {
if (canvas == null) return;

resetCanvas(canvas);
drawBigKeys(canvas);
// Small keys drawn after big keys to ensure z-index
drawSmallKeys(canvas);

// draw main, big keys (even key index)
for (int i = 0; i < piano.get_keys_count(); i += 2) {
drawKey(canvas, i);
}

// Small keys drawn after big keys to ensure z-index (odd key index)
for (int i = 1; i < piano.get_keys_count(); i += 2) {
drawKey(canvas, i);
}

// Gear icons drawn after small keys, since they go on top of those.
drawConfigGears(canvas);

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/nicobrailo/pianoli/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class Preferences {
private final static String PREF_SELECTED_SOUND_SET = "selectedSoundSet";
private final static String PREF_SELECTED_MELODIES = "selectedMelodies";
private final static String PREF_ENABLE_MELODIES = "enableMelodies";
private static final String DEFAULT_THEME = "rainbow";
public static final String DEFAULT_THEME = "rainbow";
private final static String PREF_THEME = "theme";

/**
Expand Down
180 changes: 120 additions & 60 deletions app/src/main/java/com/nicobrailo/pianoli/Theme.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,105 @@
package com.nicobrailo.pianoli;

import android.content.Context;
import android.graphics.Color;

import androidx.core.graphics.ColorUtils;

public class Theme {

/**
* Switchable key-colouring decisions for {@link PianoCanvas}.
*
* <p>
* Whenever {@link PianoCanvas} renders a key, it asks the current <code>Theme</code>-variant for the paint colour.
* This allows us to switch palettes via preferences.
* </p>
*
* @see Preferences#selectedTheme(android.content.Context)
* @see PianoCanvas#drawKey(android.graphics.Canvas, int)
*/
public enum Theme {
/**
* Boomwhackers are colour-coded pipes that produce a (tuned) note when hit.
* Their popularity in educational circles cemented their colour-to-note mapping as a de-facto international standard.
*
* <p>
* See also: <a href="https://en.wikipedia.org/wiki/Boomwhacker">wikipedia: Boomwhacker</a>, and
* <a href="https://boomwhackers.com/">boomwhackers.com</a>.
* </p>
*
*
* <p>
* Note that light green, orange and yellow have higher lightness than other colors,
* so adding just a little white doesn't have the desired effect.
* That is why they have a larger proportion of white added in.
* </p>
*/
BOOMWHACKER(new KeyColor[] {
KeyColor.createLighterWhenPressed(Color.rgb(220, 0, 0), 0.5f), // Red
KeyColor.createLighterWhenPressed(Color.rgb(255, 135, 0), 0.6f), // Orange
KeyColor.createLighterWhenPressed(Color.rgb(255, 255, 0), 0.75f), // Yellow
KeyColor.createLighterWhenPressed(Color.rgb(80, 220, 20), 0.6f), // Light Green
KeyColor.createLighterWhenPressed(Color.rgb(0, 150, 150), 0.5f), // Dark Green
KeyColor.createLighterWhenPressed(Color.rgb(95, 70, 165), 0.5f), // Purple
KeyColor.createLighterWhenPressed(Color.rgb(213, 43, 149), 0.5f), // Pink
}),

/**
* Soft pastel tones, derived from <a href="https://colorbrewer2.org/#type=qualitative&scheme=Pastel1&n=8">Colorbrewer2.org: Pastel</a>.
*/
PASTEL(new KeyColor[] {
KeyColor.createLighterWhenPressed(0xfffbb4ae, 0.5f), // dark pink
KeyColor.createLighterWhenPressed(0xffb3cde3, 0.5f), // powder blue
KeyColor.createLighterWhenPressed(0xffccebc5, 0.5f), // pistachio green
KeyColor.createLighterWhenPressed(0xffdecbe4, 0.5f), // lavender
KeyColor.createLighterWhenPressed(0xfffed9a6, 0.5f), // orange
KeyColor.createLighterWhenPressed(0xffffffcc, 0.5f), // pale yellow
KeyColor.createLighterWhenPressed(0xffe5d8bd, 0.5f), // light pink
}),

/**
* All the colours of the rainbow, C1 dark blue, C2 red, then looping back to blue.
*/
RAINBOW(new KeyColor[] {
KeyColor.createLighterWhenPressed(0xff001caf, 0.5f), // darkblue
KeyColor.createLighterWhenPressed(0xff0099ff, 0.5f), // lightblue
KeyColor.createLighterWhenPressed(0xff63c624, 0.5f), // darkgreen
KeyColor.createLighterWhenPressed(0xffbde53d, 0.5f), // lightgreen
KeyColor.createLighterWhenPressed(0xfffcc000, 0.5f), // yellow
KeyColor.createLighterWhenPressed(0xffff810a, 0.5f), // lightorange
KeyColor.createLighterWhenPressed(0xffff5616, 0.5f), // darkorange
KeyColor.createLighterWhenPressed(0xffd51016, 0.5f), // red
}),

/**
* "classic" Ivory and Black.
*/
BLACK_AND_WHITE(new KeyColor[] {
new KeyColor( // white, lighter
Color.rgb(240, 240, 240), // normal: slightly muted white;
Color.rgb(200, 200, 200) // darker gray when pressed
)
}); // note that the black flat keys are implicit and hardcoded for all themes at the moment.

/**
* Prefix for preferences-values and translation strings.
*
* <p>
* Often used implicitly, so don't forget to do full-text searches across the project when changing this.
* </p>
*/
public static final String PREFIX = "theme_";

/**
* The sequence of colors to render; repeats from the start if there are more keys than array entries.
*/
private final KeyColor[] colors;

public static Theme fromPreferences(Context context) {
String selectedTheme = Preferences.selectedTheme(context);
public static Theme fromPreference(String selectedTheme) {
// defensive programming: if we ever mess up our preferences handling, it's better to fall back to default,
// than to crash the app.
if (selectedTheme == null) {
return RAINBOW;
}

switch (selectedTheme) {
case "black_and_white":
return BLACK_AND_WHITE;
Expand All @@ -27,70 +115,43 @@ public static Theme fromPreferences(Context context) {
}
}

/**
* Note that light green, orange and yellow have higher lightness than other colors,
* so adding just a little white doesn't have the desired effect.
* That is why they have a larger proportion of white added in.
*/
private static final Theme BOOMWHACKER = new Theme(
new KeyColor[] {
KeyColor.createLighterWhenPressed(Color.rgb(220, 0, 0), 0.5f), // Red
KeyColor.createLighterWhenPressed(Color.rgb(255, 135, 0), 0.6f), // Orange
KeyColor.createLighterWhenPressed(Color.rgb(255, 255, 0), 0.75f), // Yellow
KeyColor.createLighterWhenPressed(Color.rgb(80, 220, 20), 0.6f), // Light Green
KeyColor.createLighterWhenPressed(Color.rgb(0, 150, 150), 0.5f), // Dark Green
KeyColor.createLighterWhenPressed(Color.rgb(95, 70, 165), 0.5f), // Purple
KeyColor.createLighterWhenPressed(Color.rgb(213, 43, 149), 0.5f), // Pink
}
);

private static final Theme PASTEL = new Theme(
new KeyColor[] {
// https://colorbrewer2.org/#type=qualitative&scheme=Pastel1&n=8
KeyColor.createLighterWhenPressed(0xfffbb4ae, 0.5f),
KeyColor.createLighterWhenPressed(0xffb3cde3, 0.5f),
KeyColor.createLighterWhenPressed(0xffccebc5, 0.5f),
KeyColor.createLighterWhenPressed(0xffdecbe4, 0.5f),
KeyColor.createLighterWhenPressed(0xfffed9a6, 0.5f),
KeyColor.createLighterWhenPressed(0xffffffcc, 0.5f),
KeyColor.createLighterWhenPressed(0xffe5d8bd, 0.5f),
}
);

private static final Theme RAINBOW = new Theme(
new KeyColor[] {
KeyColor.createLighterWhenPressed(0xff001caf, 0.5f), // darkblue
KeyColor.createLighterWhenPressed(0xff0099ff, 0.5f), // lightblue
KeyColor.createLighterWhenPressed(0xff63c624, 0.5f), // darkgreen
KeyColor.createLighterWhenPressed(0xffbde53d, 0.5f), // lightgreen
KeyColor.createLighterWhenPressed(0xfffcc000, 0.5f), // yellow
KeyColor.createLighterWhenPressed(0xffff810a, 0.5f), // lightorange
KeyColor.createLighterWhenPressed(0xffff5616, 0.5f), // darkorange
KeyColor.createLighterWhenPressed(0xffd51016, 0.5f), // red
}
);

private static final Theme BLACK_AND_WHITE = new Theme(
new KeyColor[] {
new KeyColor(
Color.rgb(240, 240, 240),
Color.rgb(200, 200, 200)
)
}
);

private Theme(KeyColor[] colors) {


Theme(KeyColor[] colors) {
this.colors = colors;
}

public int getColorForKey(int keyIndex, boolean isPressed) {
final int col_idx = (keyIndex / 2) % colors.length;
if ((keyIndex & 1) == 1) { // odd index = black/flat/small key
return isPressed ? Color.GRAY : 0xFF333333; // hardcoded "black" for now, but theme-able in the future.
}

final int col_idx = (keyIndex / 2) % colors.length; // divide by two to skip 'flat'/black keys at odd positions.
final KeyColor color = colors[col_idx];
return isPressed ? color.pressed : color.normal;
}

/**
* The render-colours for a big piano key: {@link #normal} and {@link #pressed}.
*
* <p>
* Note that this only applies to big keys, the 'flat' keys are always black.
* </p>
*/
private static class KeyColor {
/** The normal rendering color, when the key is inactive */
public final int normal;

/**
* The pressed/touched color of a key.
*
* <p>
* Not automatically derived from {@link #normal}, because different hues need different amounts of
* real lightening for the same amount of subjective lightening.
* </p>
*
* @see #createLighterWhenPressed(int, float)
*/
public final int pressed;

public KeyColor(int normal, int pressed) {
Expand All @@ -102,5 +163,4 @@ public static KeyColor createLighterWhenPressed(int color, float blendWhiteFacto
return new KeyColor(color, ColorUtils.blendARGB(color, Color.WHITE, blendWhiteFactor));
}
}

}
Loading

0 comments on commit d17f656

Please sign in to comment.