Merge remote-tracking branch 'origin/master' into gtk3
This commit is contained in:
commit
50326f4058
@ -286,6 +286,8 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
|
||||
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
|
||||
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
|
||||
GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
|
||||
GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
|
||||
GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
|
||||
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
|
||||
[self updatePalette];
|
||||
GB_set_rgb_encode_callback(&gb, rgbEncode);
|
||||
@ -689,6 +691,16 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
||||
name:@"GBColorCorrectionChanged"
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(updateLightTemperature)
|
||||
name:@"GBLightTemperatureChanged"
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(updateInterferenceVolume)
|
||||
name:@"GBInterferenceVolumeChanged"
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(updateFrameBlendingMode)
|
||||
name:@"GBFrameBlendingModeChanged"
|
||||
@ -1835,6 +1847,20 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateLightTemperature
|
||||
{
|
||||
if (GB_is_inited(&gb)) {
|
||||
GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateInterferenceVolume
|
||||
{
|
||||
if (GB_is_inited(&gb)) {
|
||||
GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateFrameBlendingMode
|
||||
{
|
||||
self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
|
||||
|
@ -17,7 +17,8 @@
|
||||
@property (strong) IBOutlet NSMenuItem *bootROMsFolderItem;
|
||||
@property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
|
||||
@property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
|
||||
|
||||
@property (weak) IBOutlet NSSlider *temperatureSlider;
|
||||
@property (weak) IBOutlet NSSlider *interferenceSlider;
|
||||
@property (weak) IBOutlet NSPopUpButton *dmgPopupButton;
|
||||
@property (weak) IBOutlet NSPopUpButton *sgbPopupButton;
|
||||
@property (weak) IBOutlet NSPopUpButton *cgbPopupButton;
|
||||
|
@ -26,6 +26,8 @@
|
||||
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
|
||||
NSPopUpButton *_preferredJoypadButton;
|
||||
NSPopUpButton *_rumbleModePopupButton;
|
||||
NSSlider *_temperatureSlider;
|
||||
NSSlider *_interferenceSlider;
|
||||
}
|
||||
|
||||
+ (NSArray *)filterList
|
||||
@ -91,11 +93,34 @@
|
||||
[_colorCorrectionPopupButton selectItemAtIndex:mode];
|
||||
}
|
||||
|
||||
|
||||
- (NSPopUpButton *)colorCorrectionPopupButton
|
||||
{
|
||||
return _colorCorrectionPopupButton;
|
||||
}
|
||||
|
||||
- (void)setTemperatureSlider:(NSSlider *)temperatureSlider
|
||||
{
|
||||
_temperatureSlider = temperatureSlider;
|
||||
[temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256];
|
||||
}
|
||||
|
||||
- (NSSlider *)temperatureSlider
|
||||
{
|
||||
return _temperatureSlider;
|
||||
}
|
||||
|
||||
- (void)setInterferenceSlider:(NSSlider *)interferenceSlider
|
||||
{
|
||||
_interferenceSlider = interferenceSlider;
|
||||
[interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256];
|
||||
}
|
||||
|
||||
- (NSSlider *)interferenceSlider
|
||||
{
|
||||
return _interferenceSlider;
|
||||
}
|
||||
|
||||
- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton
|
||||
{
|
||||
_frameBlendingModePopupButton = frameBlendingModePopupButton;
|
||||
@ -284,6 +309,21 @@
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)lightTemperatureChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
|
||||
forKey:@"GBLightTemperature"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)volumeTemperatureChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
|
||||
forKey:@"GBInterferenceVolume"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil];
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)franeBlendingModeChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
|
@ -73,21 +73,23 @@
|
||||
<outlet property="frameBlendingModePopupButton" destination="lxk-db-Sxv" id="wzt-uo-TE6"/>
|
||||
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
|
||||
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
|
||||
<outlet property="interferenceSlider" destination="FpE-5i-j5L" id="hfH-e8-7cx"/>
|
||||
<outlet property="playerListButton" destination="gWx-7h-0xq" id="zo6-82-JId"/>
|
||||
<outlet property="preferredJoypadButton" destination="0Az-0R-oNw" id="7JM-tw-BhK"/>
|
||||
<outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/>
|
||||
<outlet property="rumbleModePopupButton" destination="Ogs-xG-b4b" id="vuw-VN-MTp"/>
|
||||
<outlet property="sgbPopupButton" destination="dza-T7-RkX" id="B0o-Nb-pIH"/>
|
||||
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
|
||||
<outlet property="temperatureSlider" destination="NuA-mL-AJZ" id="w11-n7-Bmj"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="183" y="354"/>
|
||||
</window>
|
||||
<customView id="sRK-wO-K6R">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="323"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="378"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
|
||||
<rect key="frame" x="18" y="286" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="341" width="256" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -96,7 +98,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
|
||||
<rect key="frame" x="30" y="254" width="234" height="26"/>
|
||||
<rect key="frame" x="30" y="308" width="234" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -133,7 +135,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
|
||||
<rect key="frame" x="18" y="232" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="286" width="256" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -142,7 +144,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
|
||||
<rect key="frame" x="30" y="200" width="234" height="26"/>
|
||||
<rect key="frame" x="30" y="254" width="234" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -173,7 +175,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
|
||||
<rect key="frame" x="32" y="149" width="229" height="22"/>
|
||||
<rect key="frame" x="30" y="149" width="231" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -262,8 +264,25 @@
|
||||
<action selector="changeAspectRatio:" target="QvC-M9-y7g" id="mQG-Ib-1jN"/>
|
||||
</connections>
|
||||
</button>
|
||||
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NuA-mL-AJZ">
|
||||
<rect key="frame" x="30" y="207" width="230" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<sliderCell key="cell" continuous="YES" state="on" alignment="left" minValue="-256" maxValue="256" tickMarkPosition="below" numberOfTickMarks="3" sliderType="linear" id="KX7-G9-k0O"/>
|
||||
<connections>
|
||||
<action selector="lightTemperatureChanged:" target="QvC-M9-y7g" id="he8-ib-I3Y"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cCm-Oa-FbN">
|
||||
<rect key="frame" x="18" y="232" width="252" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Ambient light temperature:" id="Lso-GQ-pBl">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="-176" y="667.5"/>
|
||||
<point key="canvasLocation" x="-176" y="667"/>
|
||||
</customView>
|
||||
<customView id="ymk-46-SX7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="320"/>
|
||||
@ -428,11 +447,11 @@
|
||||
<point key="canvasLocation" x="-176" y="848"/>
|
||||
</customView>
|
||||
<customView id="Zn1-Y5-RbR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="86"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="134"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
|
||||
<rect key="frame" x="30" y="17" width="233" height="26"/>
|
||||
<rect key="frame" x="30" y="65" width="233" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -452,7 +471,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
|
||||
<rect key="frame" x="18" y="49" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="97" width="256" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass filter:" id="YLF-RL-b2D">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -460,8 +479,25 @@
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GPt-9I-QBh">
|
||||
<rect key="frame" x="18" y="43" width="252" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Interference volume:" id="I2Q-6U-uIx">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FpE-5i-j5L">
|
||||
<rect key="frame" x="30" y="18" width="232" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="256" tickMarkPosition="below" sliderType="linear" id="Rbx-DU-xYf"/>
|
||||
<connections>
|
||||
<action selector="volumeTemperatureChanged:" target="QvC-M9-y7g" id="HFU-0q-hj1"/>
|
||||
</connections>
|
||||
</slider>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="-176" y="890"/>
|
||||
<point key="canvasLocation" x="-176" y="914"/>
|
||||
</customView>
|
||||
<customView id="8TU-6J-NCg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="467"/>
|
||||
@ -490,7 +526,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
|
||||
<rect key="frame" x="1" y="1" width="238" height="209"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="238" height="209"/>
|
||||
|
63
Core/apu.c
63
Core/apu.c
@ -137,6 +137,45 @@ static double smooth(double x)
|
||||
return 3*x*x - 2*x*x*x;
|
||||
}
|
||||
|
||||
static signed interference(GB_gameboy_t *gb)
|
||||
{
|
||||
/* These aren't scientifically measured, but based on ear based on several recordings */
|
||||
signed ret = 0;
|
||||
if (gb->halted) {
|
||||
if (gb->model != GB_MODEL_AGB) {
|
||||
ret -= MAX_CH_AMP / 5;
|
||||
}
|
||||
else {
|
||||
ret -= MAX_CH_AMP / 12;
|
||||
}
|
||||
}
|
||||
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
|
||||
ret += MAX_CH_AMP / 7;
|
||||
if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model != GB_MODEL_AGB) {
|
||||
ret += MAX_CH_AMP / 14;
|
||||
}
|
||||
else if ((gb->io_registers[GB_IO_STAT] & 3) == 1) {
|
||||
ret -= MAX_CH_AMP / 7;
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->apu.global_enable) {
|
||||
ret += MAX_CH_AMP / 10;
|
||||
}
|
||||
|
||||
if (GB_is_cgb(gb) && gb->model < GB_MODEL_AGB && (gb->io_registers[GB_IO_RP] & 1)) {
|
||||
ret += MAX_CH_AMP / 10;
|
||||
}
|
||||
|
||||
if (!GB_is_cgb(gb)) {
|
||||
ret /= 4;
|
||||
}
|
||||
|
||||
ret += rand() % (MAX_CH_AMP / 12);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void render(GB_gameboy_t *gb)
|
||||
{
|
||||
GB_sample_t output = {0, 0};
|
||||
@ -226,6 +265,17 @@ static void render(GB_gameboy_t *gb)
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (gb->apu_output.interference_volume) {
|
||||
signed interference_bias = interference(gb);
|
||||
int16_t interference_sample = (interference_bias - gb->apu_output.interference_highpass);
|
||||
gb->apu_output.interference_highpass = gb->apu_output.interference_highpass * gb->apu_output.highpass_rate +
|
||||
(1 - gb->apu_output.highpass_rate) * interference_sample;
|
||||
interference_bias *= gb->apu_output.interference_volume;
|
||||
|
||||
filtered_output.left = MAX(MIN(filtered_output.left + interference_bias, 0x7FFF), -0x8000);
|
||||
filtered_output.right = MAX(MIN(filtered_output.right + interference_bias, 0x7FFF), -0x8000);
|
||||
}
|
||||
assert(gb->apu_output.sample_callback);
|
||||
gb->apu_output.sample_callback(gb, &filtered_output);
|
||||
}
|
||||
@ -474,7 +524,8 @@ void GB_apu_run(GB_gameboy_t *gb)
|
||||
gb->apu.noise_channel.alignment += cycles;
|
||||
|
||||
if (gb->apu.square_sweep_calculate_countdown &&
|
||||
((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.square_sweep_calculate_countdown <= 7)) { // Calculation is paused if the lower bits
|
||||
((gb->io_registers[GB_IO_NR10] & 7) ||
|
||||
gb->apu.square_sweep_calculate_countdown <= (gb->model > GB_MODEL_CGB_C? 3 : 1))) { // Calculation is paused if the lower bits
|
||||
if (gb->apu.square_sweep_calculate_countdown > cycles) {
|
||||
gb->apu.square_sweep_calculate_countdown -= cycles;
|
||||
}
|
||||
@ -823,7 +874,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
||||
gb->apu.shadow_sweep_sample_length = 0;
|
||||
if (gb->io_registers[GB_IO_NR10] & 7) {
|
||||
/* APU bug: if shift is nonzero, overflow check also occurs on trigger */
|
||||
gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div;
|
||||
gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div;
|
||||
if (gb->model > GB_MODEL_CGB_C) {
|
||||
gb->apu.square_sweep_calculate_countdown += 2;
|
||||
}
|
||||
gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length;
|
||||
gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7);
|
||||
}
|
||||
@ -1118,3 +1172,8 @@ void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb)
|
||||
gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */
|
||||
}
|
||||
}
|
||||
|
||||
void GB_set_interference_volume(GB_gameboy_t *gb, double volume)
|
||||
{
|
||||
gb->apu_output.interference_volume = volume;
|
||||
}
|
||||
|
@ -154,12 +154,16 @@ typedef struct {
|
||||
GB_sample_callback_t sample_callback;
|
||||
|
||||
bool rate_set_in_clocks;
|
||||
double interference_volume;
|
||||
double interference_highpass;
|
||||
} GB_apu_output_t;
|
||||
|
||||
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
|
||||
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
|
||||
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
|
||||
void GB_set_interference_volume(GB_gameboy_t *gb, double volume);
|
||||
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
|
||||
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "gb.h"
|
||||
|
||||
/* FIFO functions */
|
||||
@ -208,6 +209,26 @@ static void display_vblank(GB_gameboy_t *gb)
|
||||
GB_timing_sync(gb);
|
||||
}
|
||||
|
||||
static inline void temperature_tint(double temperature, double *r, double *g, double *b)
|
||||
{
|
||||
if (temperature >= 0) {
|
||||
*r = 1;
|
||||
*g = pow(1 - temperature, 0.375);
|
||||
if (temperature >= 0.75) {
|
||||
*b = 0;
|
||||
}
|
||||
else {
|
||||
*b = sqrt(0.75 - temperature);
|
||||
}
|
||||
}
|
||||
else {
|
||||
*b = 1;
|
||||
double squared = pow(temperature, 2);
|
||||
*g = 0.125 * squared + 0.3 * temperature + 1.0;
|
||||
*r = 0.21875 * squared + 0.5 * temperature + 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t scale_channel(uint8_t x)
|
||||
{
|
||||
return (x << 3) | (x >> 2);
|
||||
@ -215,12 +236,12 @@ static inline uint8_t scale_channel(uint8_t x)
|
||||
|
||||
static inline uint8_t scale_channel_with_curve(uint8_t x)
|
||||
{
|
||||
return (uint8_t[]){0,5,8,11,16,22,28,36,43,51,59,67,77,87,97,107,119,130,141,153,166,177,188,200,209,221,230,238,245,249,252,255}[x];
|
||||
return (uint8_t[]){0,6,12,20,28,36,45,56,66,76,88,100,113,125,137,149,161,172,182,192,202,210,218,225,232,238,243,247,250,252,254,255}[x];
|
||||
}
|
||||
|
||||
static inline uint8_t scale_channel_with_curve_agb(uint8_t x)
|
||||
{
|
||||
return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x];
|
||||
return (uint8_t[]){0,3,8,14,20,26,33,40,47,54,62,70,78,86,94,103,112,120,129,138,147,157,166,176,185,195,205,215,225,235,245,255}[x];
|
||||
}
|
||||
|
||||
static inline uint8_t scale_channel_with_curve_sgb(uint8_t x)
|
||||
@ -240,13 +261,12 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
|
||||
g = scale_channel(g);
|
||||
b = scale_channel(b);
|
||||
}
|
||||
else if (GB_is_sgb(gb) || for_border) {
|
||||
r = scale_channel_with_curve_sgb(r);
|
||||
g = scale_channel_with_curve_sgb(g);
|
||||
b = scale_channel_with_curve_sgb(b);
|
||||
}
|
||||
else {
|
||||
if (GB_is_sgb(gb) || for_border) {
|
||||
return gb->rgb_encode_callback(gb,
|
||||
scale_channel_with_curve_sgb(r),
|
||||
scale_channel_with_curve_sgb(g),
|
||||
scale_channel_with_curve_sgb(b));
|
||||
}
|
||||
bool agb = gb->model == GB_MODEL_AGB;
|
||||
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
|
||||
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
|
||||
@ -301,6 +321,14 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->light_temperature) {
|
||||
double light_r, light_g, light_b;
|
||||
temperature_tint(gb->light_temperature, &light_r, &light_g, &light_b);
|
||||
r = round(light_r * r);
|
||||
g = round(light_g * g);
|
||||
b = round(light_b * b);
|
||||
}
|
||||
|
||||
return gb->rgb_encode_callback(gb, r, g, b);
|
||||
}
|
||||
|
||||
@ -324,6 +352,17 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m
|
||||
}
|
||||
}
|
||||
|
||||
void GB_set_light_temperature(GB_gameboy_t *gb, double temperature)
|
||||
{
|
||||
gb->light_temperature = temperature;
|
||||
if (GB_is_cgb(gb)) {
|
||||
for (unsigned i = 0; i < 32; i++) {
|
||||
GB_palette_changed(gb, false, i * 2);
|
||||
GB_palette_changed(gb, true, i * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
STAT interrupt is implemented based on this finding:
|
||||
http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531
|
||||
@ -821,7 +860,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
GB_STATE(gb, display, 28);
|
||||
GB_STATE(gb, display, 29);
|
||||
GB_STATE(gb, display, 30);
|
||||
// GB_STATE(gb, display, 31);
|
||||
GB_STATE(gb, display, 31);
|
||||
GB_STATE(gb, display, 32);
|
||||
GB_STATE(gb, display, 33);
|
||||
GB_STATE(gb, display, 34);
|
||||
@ -853,13 +892,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
/* Handle mode 2 on the very first line 0 */
|
||||
gb->current_line = 0;
|
||||
gb->window_y = -1;
|
||||
/* Todo: verify timings */
|
||||
if (gb->io_registers[GB_IO_WY] == 0) {
|
||||
gb->wy_triggered = true;
|
||||
}
|
||||
else {
|
||||
gb->wy_triggered = false;
|
||||
}
|
||||
gb->wy_triggered = false;
|
||||
|
||||
gb->ly_for_comparison = 0;
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
@ -910,11 +943,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
/* Lines 0 - 143 */
|
||||
gb->window_y = -1;
|
||||
for (; gb->current_line < LINES; gb->current_line++) {
|
||||
/* Todo: verify timings */
|
||||
if ((gb->io_registers[GB_IO_WY] == gb->current_line ||
|
||||
(gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) {
|
||||
gb->wy_triggered = true;
|
||||
}
|
||||
|
||||
gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed;
|
||||
gb->accessed_oam_row = 0;
|
||||
@ -994,6 +1022,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
gb->cycles_for_line += 2;
|
||||
GB_SLEEP(gb, display, 32, 2);
|
||||
mode_3_start:
|
||||
/* TODO: Timing seems incorrect, might need an access conflict handling. */
|
||||
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
|
||||
gb->io_registers[GB_IO_WY] == gb->current_line) {
|
||||
gb->wy_triggered = true;
|
||||
}
|
||||
|
||||
fifo_clear(&gb->bg_fifo);
|
||||
fifo_clear(&gb->oam_fifo);
|
||||
@ -1021,7 +1054,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
bool should_activate_window = false;
|
||||
if (gb->io_registers[GB_IO_WX] == 0) {
|
||||
static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14};
|
||||
if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7] && !GB_is_cgb(gb)) {
|
||||
if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) {
|
||||
should_activate_window = true;
|
||||
}
|
||||
}
|
||||
@ -1243,7 +1276,16 @@ abort_fetching_object:
|
||||
if (gb->hdma_on_hblank) {
|
||||
gb->hdma_starting = true;
|
||||
}
|
||||
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
|
||||
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2);
|
||||
/*
|
||||
TODO: Verify double speed timing
|
||||
TODO: Timing differs on a DMG
|
||||
*/
|
||||
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
|
||||
(gb->io_registers[GB_IO_WY] == gb->current_line)) {
|
||||
gb->wy_triggered = true;
|
||||
}
|
||||
GB_SLEEP(gb, display, 31, 2);
|
||||
gb->mode_for_interrupt = 2;
|
||||
|
||||
// Todo: unverified timing
|
||||
@ -1337,14 +1379,7 @@ abort_fetching_object:
|
||||
|
||||
|
||||
gb->current_line = 0;
|
||||
/* Todo: verify timings */
|
||||
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
|
||||
(gb->io_registers[GB_IO_WY] == 0)) {
|
||||
gb->wy_triggered = true;
|
||||
}
|
||||
else {
|
||||
gb->wy_triggered = false;
|
||||
}
|
||||
gb->wy_triggered = false;
|
||||
|
||||
// TODO: not the correct timing
|
||||
gb->current_lcd_line = 0;
|
||||
|
@ -58,5 +58,6 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette
|
||||
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height);
|
||||
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border);
|
||||
void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode);
|
||||
void GB_set_light_temperature(GB_gameboy_t *gb, double temperature);
|
||||
bool GB_is_odd_frame(GB_gameboy_t *gb);
|
||||
#endif /* display_h */
|
||||
|
@ -305,6 +305,9 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path)
|
||||
fread(gb->rom, 1, gb->rom_size, f);
|
||||
fclose(f);
|
||||
GB_configure_cart(gb);
|
||||
gb->tried_loading_sgb_border = false;
|
||||
gb->has_sgb_border = false;
|
||||
load_default_border(gb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -537,6 +540,9 @@ error:
|
||||
gb->rom_size = old_size;
|
||||
}
|
||||
fclose(f);
|
||||
gb->tried_loading_sgb_border = false;
|
||||
gb->has_sgb_border = false;
|
||||
load_default_border(gb);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -557,6 +563,9 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz
|
||||
memset(gb->rom, 0xff, gb->rom_size);
|
||||
memcpy(gb->rom, buffer, size);
|
||||
GB_configure_cart(gb);
|
||||
gb->tried_loading_sgb_border = false;
|
||||
gb->has_sgb_border = false;
|
||||
load_default_border(gb);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
|
@ -573,6 +573,7 @@ struct GB_gameboy_internal_s {
|
||||
uint32_t sprite_palettes_rgb[0x20];
|
||||
const GB_palette_t *dmg_palette;
|
||||
GB_color_correction_mode_t color_correction_mode;
|
||||
double light_temperature;
|
||||
bool keys[4][GB_KEY_MAX];
|
||||
GB_border_mode_t border_mode;
|
||||
GB_sgb_border_t borrowed_border;
|
||||
|
@ -895,6 +895,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
||||
gb->display_cycles = 0;
|
||||
gb->display_state = 0;
|
||||
gb->double_speed_alignment = 0;
|
||||
if (GB_is_sgb(gb)) {
|
||||
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
||||
}
|
||||
|
@ -249,6 +249,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
// Todo: This is difference is because my timing is off in one of the models
|
||||
if (gb->model > GB_MODEL_CGB_C) {
|
||||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first
|
||||
gb->tile_sel_glitch = true;
|
||||
GB_advance_cycles(gb, 1);
|
||||
gb->tile_sel_glitch = false;
|
||||
@ -257,6 +258,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
}
|
||||
else {
|
||||
GB_advance_cycles(gb, gb->pending_cycles - 1);
|
||||
GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first
|
||||
gb->tile_sel_glitch = true;
|
||||
GB_advance_cycles(gb, 1);
|
||||
gb->tile_sel_glitch = false;
|
||||
@ -330,6 +332,7 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode)
|
||||
|
||||
static void enter_stop_mode(GB_gameboy_t *gb)
|
||||
{
|
||||
GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0);
|
||||
gb->stopped = true;
|
||||
gb->oam_ppu_blocked = !gb->oam_read_blocked;
|
||||
gb->vram_ppu_blocked = !gb->vram_read_blocked;
|
||||
@ -338,14 +341,16 @@ static void enter_stop_mode(GB_gameboy_t *gb)
|
||||
|
||||
static void leave_stop_mode(GB_gameboy_t *gb)
|
||||
{
|
||||
/* The CPU takes more time to wake up then the other components */
|
||||
for (unsigned i = 0x200; i--;) {
|
||||
GB_advance_cycles(gb, 0x10);
|
||||
}
|
||||
gb->stopped = false;
|
||||
gb->oam_ppu_blocked = false;
|
||||
gb->vram_ppu_blocked = false;
|
||||
gb->cgb_palettes_ppu_blocked = false;
|
||||
/* The CPU takes more time to wake up then the other components */
|
||||
for (unsigned i = 0x1FFF; i--;) {
|
||||
GB_advance_cycles(gb, 0x10);
|
||||
}
|
||||
GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xF);
|
||||
GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0);
|
||||
}
|
||||
|
||||
static void stop(GB_gameboy_t *gb, uint8_t opcode)
|
||||
@ -356,9 +361,11 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
|
||||
|
||||
GB_advance_cycles(gb, 0x4);
|
||||
/* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */
|
||||
|
||||
if (gb->double_speed_alignment & 7) {
|
||||
GB_advance_cycles(gb, 0x4);
|
||||
needs_alignment = true;
|
||||
GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n");
|
||||
}
|
||||
|
||||
gb->cgb_double_speed ^= true;
|
||||
@ -375,16 +382,14 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
|
||||
else {
|
||||
GB_timing_sync(gb);
|
||||
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
|
||||
/* HW Bug? When STOP is executed while a button is down, the CPU halts forever
|
||||
yet the other hardware keeps running. */
|
||||
gb->interrupt_enable = 0;
|
||||
/* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt
|
||||
mode instead. Fine details not confirmed yet. */
|
||||
gb->halted = true;
|
||||
}
|
||||
else {
|
||||
enter_stop_mode(gb);
|
||||
}
|
||||
}
|
||||
|
||||
/* Todo: is PC being actually read? */
|
||||
gb->pc++;
|
||||
}
|
||||
|
@ -157,7 +157,9 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
|
||||
static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
{
|
||||
if (gb->stopped) {
|
||||
gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -248,7 +250,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
||||
}
|
||||
|
||||
// Not affected by speed boost
|
||||
gb->double_speed_alignment += cycles;
|
||||
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
|
||||
gb->double_speed_alignment += cycles;
|
||||
}
|
||||
gb->hdma_cycles += cycles;
|
||||
gb->apu_output.sample_cycles += cycles;
|
||||
gb->cycles_since_last_sync += cycles;
|
||||
|
4
Makefile
4
Makefile
@ -99,7 +99,7 @@ endif
|
||||
|
||||
# These must come before the -Wno- flags
|
||||
WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option
|
||||
WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context
|
||||
WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation
|
||||
|
||||
# Only add this flag if the compiler supports it
|
||||
ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0)
|
||||
@ -157,7 +157,7 @@ endif
|
||||
|
||||
ifeq ($(PLATFORM),windows32)
|
||||
CFLAGS += -IWindows -Drandom=rand --target=i386-pc-windows
|
||||
LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows
|
||||
LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lole32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows
|
||||
SDL_LDFLAGS := -lSDL2
|
||||
GL_LDFLAGS := -lopengl32
|
||||
else
|
||||
|
@ -18,3 +18,21 @@ char *do_open_rom_dialog(void)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *do_open_folder_dialog(void)
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSWindow *key = [NSApp keyWindow];
|
||||
NSOpenPanel *dialog = [NSOpenPanel openPanel];
|
||||
dialog.title = @"Select Boot ROMs Folder";
|
||||
dialog.canChooseDirectories = true;
|
||||
dialog.canChooseFiles = false;
|
||||
[dialog runModal];
|
||||
[key makeKeyAndOrderFront:nil];
|
||||
NSString *ret = [[[dialog URLs] firstObject] path];
|
||||
if (ret) {
|
||||
return strdup(ret.UTF8String);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#define GTK_FILE_CHOOSER_ACTION_OPEN 0
|
||||
#define GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 2
|
||||
#define GTK_RESPONSE_ACCEPT -3
|
||||
#define GTK_RESPONSE_CANCEL -6
|
||||
|
||||
@ -111,3 +112,71 @@ lazy_error:
|
||||
fprintf(stderr, "Failed to display GTK dialog\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *do_open_folder_dialog(void)
|
||||
{
|
||||
static void *handle = NULL;
|
||||
|
||||
TRY_DLOPEN("libgtk-3.so");
|
||||
TRY_DLOPEN("libgtk-3.so.0");
|
||||
TRY_DLOPEN("libgtk-2.so");
|
||||
TRY_DLOPEN("libgtk-2.so.0");
|
||||
|
||||
if (!handle) {
|
||||
goto lazy_error;
|
||||
}
|
||||
|
||||
|
||||
LAZY(gtk_init_check);
|
||||
LAZY(gtk_file_chooser_dialog_new);
|
||||
LAZY(gtk_dialog_run);
|
||||
LAZY(g_free);
|
||||
LAZY(gtk_widget_destroy);
|
||||
LAZY(gtk_file_chooser_get_filename);
|
||||
LAZY(g_log_set_default_handler);
|
||||
LAZY(gtk_file_filter_new);
|
||||
LAZY(gtk_file_filter_add_pattern);
|
||||
LAZY(gtk_file_filter_set_name);
|
||||
LAZY(gtk_file_chooser_add_filter);
|
||||
LAZY(gtk_events_pending);
|
||||
LAZY(gtk_main_iteration);
|
||||
|
||||
/* Shut up GTK */
|
||||
g_log_set_default_handler(nop, NULL);
|
||||
|
||||
gtk_init_check(0, 0);
|
||||
|
||||
|
||||
void *dialog = gtk_file_chooser_dialog_new("Select Boot ROMs Folder",
|
||||
0,
|
||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT,
|
||||
NULL );
|
||||
|
||||
|
||||
int res = gtk_dialog_run (dialog);
|
||||
char *ret = NULL;
|
||||
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
char *filename;
|
||||
filename = gtk_file_chooser_get_filename(dialog);
|
||||
ret = strdup(filename);
|
||||
g_free(filename);
|
||||
}
|
||||
|
||||
while (gtk_events_pending()) {
|
||||
gtk_main_iteration();
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
|
||||
while (gtk_events_pending()) {
|
||||
gtk_main_iteration();
|
||||
}
|
||||
return ret;
|
||||
|
||||
lazy_error:
|
||||
fprintf(stderr, "Failed to display GTK dialog\n");
|
||||
return NULL;
|
||||
}
|
||||
|
@ -2,5 +2,5 @@
|
||||
#define open_rom_h
|
||||
|
||||
char *do_open_rom_dialog(void);
|
||||
|
||||
char *do_open_folder_dialog(void);
|
||||
#endif /* open_rom_h */
|
||||
|
@ -1,10 +1,11 @@
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include "open_dialog.h"
|
||||
|
||||
char *do_open_rom_dialog(void)
|
||||
{
|
||||
OPENFILENAMEW dialog;
|
||||
wchar_t filename[MAX_PATH] = {0};
|
||||
static wchar_t filename[MAX_PATH] = {0};
|
||||
|
||||
memset(&dialog, 0, sizeof(dialog));
|
||||
dialog.lStructSize = sizeof(dialog);
|
||||
@ -25,3 +26,32 @@ char *do_open_rom_dialog(void)
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *do_open_folder_dialog(void)
|
||||
{
|
||||
|
||||
BROWSEINFOW dialog;
|
||||
memset(&dialog, 0, sizeof(dialog));
|
||||
|
||||
dialog.ulFlags = BIF_USENEWUI;
|
||||
dialog.lpszTitle = L"Select Boot ROMs Folder";
|
||||
|
||||
OleInitialize(NULL);
|
||||
|
||||
LPITEMIDLIST list = SHBrowseForFolderW(&dialog);
|
||||
static wchar_t filename[MAX_PATH] = {0};
|
||||
|
||||
if (list) {
|
||||
if (!SHGetPathFromIDListW(list, filename)) {
|
||||
OleUninitialize();
|
||||
return NULL;
|
||||
}
|
||||
char *ret = malloc(MAX_PATH * 4);
|
||||
WideCharToMultiByte(CP_UTF8, 0, filename, sizeof(filename), ret, MAX_PATH * 4, NULL, NULL);
|
||||
CoTaskMemFree(list);
|
||||
OleUninitialize();
|
||||
return ret;
|
||||
}
|
||||
OleUninitialize();
|
||||
return NULL;
|
||||
}
|
||||
|
20
SDL/font.c
20
SDL/font.c
@ -1033,6 +1033,26 @@ uint8_t font[] = {
|
||||
_, _, _, X, X, _,
|
||||
_, _, _, _, X, _,
|
||||
_, _, _, _, _, _,
|
||||
|
||||
/* Elipsis */
|
||||
_, _, _, _, _, _,
|
||||
_, _, _, _, _, _,
|
||||
_, _, _, _, _, _,
|
||||
_, _, _, _, _, _,
|
||||
_, _, _, _, _, _,
|
||||
_, _, _, _, _, _,
|
||||
X, _, X, _, X, _,
|
||||
_, _, _, _, _, _,
|
||||
|
||||
/* Mojibake */
|
||||
X, X, X, X, X, _,
|
||||
X, _, _, _, X, _,
|
||||
X, _, _, _, X, _,
|
||||
X, _, _, _, X, _,
|
||||
X, _, _, _, X, _,
|
||||
X, _, _, _, X, _,
|
||||
X, _, _, _, X, _,
|
||||
X, X, X, X, X, _,
|
||||
};
|
||||
|
||||
const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' ';
|
||||
|
@ -12,5 +12,7 @@ extern const uint8_t font_max;
|
||||
#define CTRL_STRING "\x80\x81\x82"
|
||||
#define SHIFT_STRING "\x83"
|
||||
#define CMD_STRING "\x84\x85"
|
||||
#define ELLIPSIS_STRING "\x87"
|
||||
#define MOJIBAKE_STRING "\x88"
|
||||
#endif /* font_h */
|
||||
|
||||
|
227
SDL/gui.c
227
SDL/gui.c
@ -110,6 +110,7 @@ configuration_t configuration =
|
||||
.volume = 100,
|
||||
.rumble_mode = GB_RUMBLE_ALL_GAMES,
|
||||
.default_scale = 2,
|
||||
.color_temperature = 10,
|
||||
};
|
||||
|
||||
|
||||
@ -176,8 +177,7 @@ static void rescale_window(void)
|
||||
SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale);
|
||||
}
|
||||
|
||||
/* Does NOT check for bounds! */
|
||||
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color)
|
||||
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color, uint32_t *mask_top, uint32_t *mask_bottom)
|
||||
{
|
||||
if (ch < ' ' || ch > font_max) {
|
||||
ch = '?';
|
||||
@ -187,7 +187,7 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne
|
||||
|
||||
for (unsigned y = GLYPH_HEIGHT; y--;) {
|
||||
for (unsigned x = GLYPH_WIDTH; x--;) {
|
||||
if (*(data++)) {
|
||||
if (*(data++) && buffer >= mask_top && buffer < mask_bottom) {
|
||||
(*buffer) = color;
|
||||
}
|
||||
buffer++;
|
||||
@ -196,8 +196,8 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned scroll = 0;
|
||||
static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color)
|
||||
static signed scroll = 0;
|
||||
static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color)
|
||||
{
|
||||
y -= scroll;
|
||||
unsigned orig_x = x;
|
||||
@ -210,17 +210,17 @@ static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned heig
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x > width - GLYPH_WIDTH || y == 0 || y - y_offset > 144 - GLYPH_HEIGHT) {
|
||||
if (x > width - GLYPH_WIDTH) {
|
||||
break;
|
||||
}
|
||||
|
||||
draw_char(&buffer[x + width * y], width, height, *string, color);
|
||||
draw_char(&buffer[(signed)(x + width * y)], width, height, *string, color, &buffer[width * y_offset], &buffer[width * (y_offset + 144)]);
|
||||
x += GLYPH_WIDTH;
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border)
|
||||
static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border)
|
||||
{
|
||||
draw_unbordered_text(buffer, width, height, x - 1, y, string, border);
|
||||
draw_unbordered_text(buffer, width, height, x + 1, y, string, border);
|
||||
@ -261,6 +261,10 @@ struct menu_item {
|
||||
};
|
||||
static const struct menu_item *current_menu = NULL;
|
||||
static const struct menu_item *root_menu = NULL;
|
||||
static unsigned menu_height;
|
||||
static unsigned scrollbar_size;
|
||||
static bool mouse_scroling = false;
|
||||
|
||||
static unsigned current_selection = 0;
|
||||
|
||||
static enum {
|
||||
@ -302,6 +306,23 @@ static void open_rom(unsigned index)
|
||||
}
|
||||
}
|
||||
|
||||
static void recalculate_menu_height(void)
|
||||
{
|
||||
menu_height = 24;
|
||||
scrollbar_size = 0;
|
||||
if (gui_state == SHOWING_MENU) {
|
||||
for (const struct menu_item *item = current_menu; item->string; item++) {
|
||||
menu_height += 12;
|
||||
if (item->backwards_handler) {
|
||||
menu_height += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (menu_height > 144) {
|
||||
scrollbar_size = 144 * 140 / menu_height;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct menu_item paused_menu[] = {
|
||||
{"Resume", NULL},
|
||||
{"Open ROM", open_rom},
|
||||
@ -322,6 +343,7 @@ static void return_to_root_menu(unsigned index)
|
||||
current_menu = root_menu;
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
recalculate_menu_height();
|
||||
}
|
||||
|
||||
static void cycle_model(unsigned index)
|
||||
@ -420,9 +442,50 @@ const char *current_rewind_string(unsigned index)
|
||||
return "Custom";
|
||||
}
|
||||
|
||||
const char *current_bootrom_string(unsigned index)
|
||||
{
|
||||
if (!configuration.bootrom_path[0]) {
|
||||
return "Built-in Boot ROMs";
|
||||
}
|
||||
size_t length = strlen(configuration.bootrom_path);
|
||||
static char ret[24] = {0,};
|
||||
if (length <= 23) {
|
||||
strcpy(ret, configuration.bootrom_path);
|
||||
}
|
||||
else {
|
||||
memcpy(ret, configuration.bootrom_path, 11);
|
||||
memcpy(ret + 12, configuration.bootrom_path + length - 11, 11);
|
||||
}
|
||||
for (unsigned i = 0; i < 24; i++) {
|
||||
if (ret[i] < 0) {
|
||||
ret[i] = MOJIBAKE_STRING[0];
|
||||
}
|
||||
}
|
||||
if (length > 23) {
|
||||
ret[11] = ELLIPSIS_STRING[0];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void toggle_bootrom(unsigned index)
|
||||
{
|
||||
if (configuration.bootrom_path[0]) {
|
||||
configuration.bootrom_path[0] = 0;
|
||||
}
|
||||
else {
|
||||
char *folder = do_open_folder_dialog();
|
||||
if (!folder) return;
|
||||
if (strlen(folder) < sizeof(configuration.bootrom_path) - 1) {
|
||||
strcpy(configuration.bootrom_path, folder);
|
||||
}
|
||||
free(folder);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct menu_item emulation_menu[] = {
|
||||
{"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards},
|
||||
{"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards},
|
||||
{"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom},
|
||||
{"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards},
|
||||
{"Back", return_to_root_menu},
|
||||
{NULL,}
|
||||
@ -433,6 +496,7 @@ static void enter_emulation_menu(unsigned index)
|
||||
current_menu = emulation_menu;
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
recalculate_menu_height();
|
||||
}
|
||||
|
||||
const char *current_scaling_mode(unsigned index)
|
||||
@ -453,6 +517,33 @@ const char *current_color_correction_mode(unsigned index)
|
||||
[configuration.color_correction_mode];
|
||||
}
|
||||
|
||||
const char *current_color_temperature(unsigned index)
|
||||
{
|
||||
return (const char *[]){"12000K",
|
||||
"11450K",
|
||||
"10900K",
|
||||
"10350K",
|
||||
"9800K",
|
||||
"9250K",
|
||||
"8700K",
|
||||
"8150K",
|
||||
"7600K",
|
||||
"7050K",
|
||||
"6500K (White)",
|
||||
"5950K",
|
||||
"5400K",
|
||||
"4850K",
|
||||
"4300K",
|
||||
"3750K",
|
||||
"3200K",
|
||||
"2650K",
|
||||
"2100K",
|
||||
"1550K",
|
||||
"1000K"}
|
||||
[configuration.color_temperature];
|
||||
}
|
||||
|
||||
|
||||
const char *current_palette(unsigned index)
|
||||
{
|
||||
return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"}
|
||||
@ -533,6 +624,20 @@ static void cycle_color_correction_backwards(unsigned index)
|
||||
}
|
||||
}
|
||||
|
||||
static void decrease_color_temperature(unsigned index)
|
||||
{
|
||||
if (configuration.color_temperature < 20) {
|
||||
configuration.color_temperature++;
|
||||
}
|
||||
}
|
||||
|
||||
static void increase_color_temperature(unsigned index)
|
||||
{
|
||||
if (configuration.color_temperature > 0) {
|
||||
configuration.color_temperature--;
|
||||
}
|
||||
}
|
||||
|
||||
static void cycle_palette(unsigned index)
|
||||
{
|
||||
if (configuration.dmg_palette == 3) {
|
||||
@ -684,6 +789,7 @@ static const struct menu_item graphics_menu[] = {
|
||||
{"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards},
|
||||
{"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards},
|
||||
{"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards},
|
||||
{"Ambient Light Temp.:", decrease_color_temperature, current_color_temperature, increase_color_temperature},
|
||||
{"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards},
|
||||
{"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards},
|
||||
{"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards},
|
||||
@ -696,6 +802,7 @@ static void enter_graphics_menu(unsigned index)
|
||||
current_menu = graphics_menu;
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
recalculate_menu_height();
|
||||
}
|
||||
|
||||
const char *highpass_filter_string(unsigned index)
|
||||
@ -745,9 +852,33 @@ void decrease_volume(unsigned index)
|
||||
}
|
||||
}
|
||||
|
||||
const char *interference_volume_string(unsigned index)
|
||||
{
|
||||
static char ret[5];
|
||||
sprintf(ret, "%d%%", configuration.interference_volume);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void increase_interference_volume(unsigned index)
|
||||
{
|
||||
configuration.interference_volume += 5;
|
||||
if (configuration.interference_volume > 100) {
|
||||
configuration.interference_volume = 100;
|
||||
}
|
||||
}
|
||||
|
||||
void decrease_interference_volume(unsigned index)
|
||||
{
|
||||
configuration.interference_volume -= 5;
|
||||
if (configuration.interference_volume > 100) {
|
||||
configuration.interference_volume = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct menu_item audio_menu[] = {
|
||||
{"Highpass Filter:", cycle_highpass_filter, highpass_filter_string, cycle_highpass_filter_backwards},
|
||||
{"Volume:", increase_volume, volume_string, decrease_volume},
|
||||
{"Interference Volume:", increase_interference_volume, interference_volume_string, decrease_interference_volume},
|
||||
{"Back", return_to_root_menu},
|
||||
{NULL,}
|
||||
};
|
||||
@ -757,6 +888,7 @@ static void enter_audio_menu(unsigned index)
|
||||
current_menu = audio_menu;
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
recalculate_menu_height();
|
||||
}
|
||||
|
||||
static void modify_key(unsigned index)
|
||||
@ -798,6 +930,7 @@ static void enter_controls_menu(unsigned index)
|
||||
current_menu = controls_menu;
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
recalculate_menu_height();
|
||||
}
|
||||
|
||||
static unsigned joypad_index = 0;
|
||||
@ -815,7 +948,7 @@ const char *current_joypad_name(unsigned index)
|
||||
// SDL returns a name with repeated and trailing spaces
|
||||
while (*orig_name && i < sizeof(name) - 2) {
|
||||
if (orig_name[0] != ' ' || orig_name[1] != ' ') {
|
||||
name[i++] = *orig_name;
|
||||
name[i++] = *orig_name > 0? *orig_name : MOJIBAKE_STRING[0];
|
||||
}
|
||||
orig_name++;
|
||||
}
|
||||
@ -933,6 +1066,7 @@ static void enter_joypad_menu(unsigned index)
|
||||
current_menu = joypad_menu;
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
recalculate_menu_height();
|
||||
}
|
||||
|
||||
joypad_button_t get_joypad_button(uint8_t physical_button)
|
||||
@ -1017,6 +1151,7 @@ void run_gui(bool is_running)
|
||||
gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE;
|
||||
bool should_render = true;
|
||||
current_menu = root_menu = is_running? paused_menu : nonpaused_menu;
|
||||
recalculate_menu_height();
|
||||
current_selection = 0;
|
||||
scroll = 0;
|
||||
do {
|
||||
@ -1204,9 +1339,26 @@ void run_gui(bool is_running)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL: {
|
||||
if (menu_height > 144) {
|
||||
scroll -= event.wheel.y;
|
||||
if (scroll < 0) {
|
||||
scroll = 0;
|
||||
}
|
||||
if (scroll >= menu_height - 144) {
|
||||
scroll = menu_height - 144;
|
||||
}
|
||||
|
||||
mouse_scroling = true;
|
||||
should_render = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.scancode == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) {
|
||||
if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) {
|
||||
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) {
|
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
}
|
||||
@ -1215,7 +1367,7 @@ void run_gui(bool is_running)
|
||||
}
|
||||
update_viewport();
|
||||
}
|
||||
if (event.key.keysym.scancode == SDL_SCANCODE_O) {
|
||||
if (event_hotkey_code(&event) == SDL_SCANCODE_O) {
|
||||
if (event.key.keysym.mod & MODIFIER) {
|
||||
char *filename = do_open_rom_dialog();
|
||||
if (filename) {
|
||||
@ -1241,7 +1393,11 @@ void run_gui(bool is_running)
|
||||
}
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
if (is_running) {
|
||||
if (gui_state == SHOWING_MENU && current_menu != root_menu) {
|
||||
return_to_root_menu(0);
|
||||
should_render = true;
|
||||
}
|
||||
else if (is_running) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@ -1252,18 +1408,22 @@ void run_gui(bool is_running)
|
||||
gui_state = SHOWING_DROP_MESSAGE;
|
||||
}
|
||||
current_selection = 0;
|
||||
mouse_scroling = false;
|
||||
scroll = 0;
|
||||
current_menu = root_menu;
|
||||
recalculate_menu_height();
|
||||
should_render = true;
|
||||
}
|
||||
}
|
||||
else if (gui_state == SHOWING_MENU) {
|
||||
if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) {
|
||||
current_selection++;
|
||||
mouse_scroling = false;
|
||||
should_render = true;
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) {
|
||||
current_selection--;
|
||||
mouse_scroling = false;
|
||||
should_render = true;
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && !current_menu[current_selection].backwards_handler) {
|
||||
@ -1340,13 +1500,21 @@ void run_gui(bool is_running)
|
||||
draw_text_centered(pixels, width, height, 8 + y_offset, "SameBoy", gui_palette_native[3], gui_palette_native[0], false);
|
||||
unsigned i = 0, y = 24;
|
||||
for (const struct menu_item *item = current_menu; item->string; item++, i++) {
|
||||
if (i == current_selection) {
|
||||
if (y < scroll) {
|
||||
scroll = y - 4;
|
||||
goto rerender;
|
||||
if (i == current_selection && !mouse_scroling) {
|
||||
if (i == 0) {
|
||||
if (y < scroll) {
|
||||
scroll = (y - 4) / 12 * 12;
|
||||
goto rerender;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (y < scroll + 24) {
|
||||
scroll = (y - 24) / 12 * 12;
|
||||
goto rerender;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == current_selection && i == 0 && scroll != 0) {
|
||||
if (i == current_selection && i == 0 && scroll != 0 && !mouse_scroling) {
|
||||
scroll = 0;
|
||||
goto rerender;
|
||||
}
|
||||
@ -1363,19 +1531,38 @@ void run_gui(bool is_running)
|
||||
i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE);
|
||||
y += 12;
|
||||
if (item->value_getter) {
|
||||
draw_text_centered(pixels, width, height, y + y_offset, item->value_getter(i), gui_palette_native[3], gui_palette_native[0],
|
||||
draw_text_centered(pixels, width, height, y + y_offset - 1, item->value_getter(i), gui_palette_native[3], gui_palette_native[0],
|
||||
i == current_selection ? DECORATION_ARROWS : DECORATION_NONE);
|
||||
y += 12;
|
||||
}
|
||||
}
|
||||
if (i == current_selection) {
|
||||
if (i == current_selection && !mouse_scroling) {
|
||||
if (y > scroll + 144) {
|
||||
scroll = y - 144;
|
||||
scroll = (y - 144) / 12 * 12;
|
||||
if (scroll > menu_height - 144) {
|
||||
scroll = menu_height - 144;
|
||||
}
|
||||
goto rerender;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (scrollbar_size) {
|
||||
unsigned scrollbar_offset = (140 - scrollbar_size) * scroll / (menu_height - 144);
|
||||
if (scrollbar_offset + scrollbar_size > 140) {
|
||||
scrollbar_offset = 140 - scrollbar_size;
|
||||
}
|
||||
for (unsigned y = 0; y < 140; y++) {
|
||||
uint32_t *pixel = pixels + x_offset + 156 + width * (y + y_offset + 2);
|
||||
if (y >= scrollbar_offset && y < scrollbar_offset + scrollbar_size) {
|
||||
pixel[0] = pixel[1]= gui_palette_native[2];
|
||||
}
|
||||
else {
|
||||
pixel[0] = pixel[1]= gui_palette_native[1];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SHOWING_HELP:
|
||||
draw_text(pixels, width, height, 2 + x_offset, 2 + y_offset, help[current_help_page], gui_palette_native[3], gui_palette_native[0]);
|
||||
|
15
SDL/gui.h
15
SDL/gui.h
@ -110,6 +110,12 @@ typedef struct {
|
||||
GB_rumble_mode_t rumble_mode;
|
||||
|
||||
uint8_t default_scale;
|
||||
|
||||
/* v0.14 */
|
||||
unsigned padding;
|
||||
uint8_t color_temperature;
|
||||
char bootrom_path[4096];
|
||||
uint8_t interference_volume;
|
||||
} configuration_t;
|
||||
|
||||
extern configuration_t configuration;
|
||||
@ -122,4 +128,13 @@ void connect_joypad(void);
|
||||
joypad_button_t get_joypad_button(uint8_t physical_button);
|
||||
joypad_axis_t get_joypad_axis(uint8_t physical_axis);
|
||||
|
||||
static SDL_Scancode event_hotkey_code(SDL_Event *event)
|
||||
{
|
||||
if (event->key.keysym.sym >= SDLK_a && event->key.keysym.sym < SDLK_z) {
|
||||
return SDL_SCANCODE_A + event->key.keysym.sym - SDLK_a;
|
||||
}
|
||||
|
||||
return event->key.keysym.scancode;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
24
SDL/main.c
24
SDL/main.c
@ -120,6 +120,8 @@ static void open_menu(void)
|
||||
GB_audio_set_paused(false);
|
||||
}
|
||||
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
|
||||
GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
|
||||
GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
|
||||
GB_set_border_mode(&gb, configuration.border_mode);
|
||||
update_palette();
|
||||
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
|
||||
@ -233,7 +235,7 @@ static void handle_events(GB_gameboy_t *gb)
|
||||
};
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
switch (event.key.keysym.scancode) {
|
||||
switch (event_hotkey_code(&event)) {
|
||||
case SDL_SCANCODE_ESCAPE: {
|
||||
open_menu();
|
||||
break;
|
||||
@ -241,7 +243,6 @@ static void handle_events(GB_gameboy_t *gb)
|
||||
case SDL_SCANCODE_C:
|
||||
if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
|
||||
GB_debugger_break(gb);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
@ -448,8 +449,6 @@ static bool handle_pending_command(void)
|
||||
|
||||
static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type)
|
||||
{
|
||||
bool error = false;
|
||||
start_capturing_logs();
|
||||
static const char *const names[] = {
|
||||
[GB_BOOT_ROM_DMG0] = "dmg0_boot.bin",
|
||||
[GB_BOOT_ROM_DMG] = "dmg_boot.bin",
|
||||
@ -460,8 +459,17 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type)
|
||||
[GB_BOOT_ROM_CGB] = "cgb_boot.bin",
|
||||
[GB_BOOT_ROM_AGB] = "agb_boot.bin",
|
||||
};
|
||||
GB_load_boot_rom(gb, resource_path(names[type]));
|
||||
end_capturing_logs(true, error);
|
||||
bool use_built_in = true;
|
||||
if (configuration.bootrom_path[0]) {
|
||||
static char path[4096];
|
||||
snprintf(path, sizeof(path), "%s/%s", configuration.bootrom_path, names[type]);
|
||||
use_built_in = GB_load_boot_rom(gb, path);
|
||||
}
|
||||
if (use_built_in) {
|
||||
start_capturing_logs();
|
||||
GB_load_boot_rom(gb, resource_path(names[type]));
|
||||
end_capturing_logs(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void run(void)
|
||||
@ -497,6 +505,8 @@ restart:
|
||||
GB_set_rumble_mode(&gb, configuration.rumble_mode);
|
||||
GB_set_sample_rate(&gb, GB_audio_get_sample_rate());
|
||||
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
|
||||
GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
|
||||
GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
|
||||
update_palette();
|
||||
if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) {
|
||||
GB_set_border_mode(&gb, configuration.border_mode);
|
||||
@ -647,6 +657,8 @@ int main(int argc, char **argv)
|
||||
configuration.dmg_palette %= 3;
|
||||
configuration.border_mode %= GB_BORDER_ALWAYS + 1;
|
||||
configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1;
|
||||
configuration.color_temperature %= 21;
|
||||
configuration.bootrom_path[sizeof(configuration.bootrom_path) - 1] = 0;
|
||||
}
|
||||
|
||||
if (configuration.model >= MODEL_MAX) {
|
||||
|
Loading…
Reference in New Issue
Block a user