2014-09-07 21:10:04 +00:00
/ *
* Copyright ( C ) 2012 Brendan Robert ( BLuRry ) brendan . robert @gmail.com.
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 . 1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston ,
* MA 02110 - 1301 USA
* /
package jace.apple2e ;
2016-02-21 08:22:47 +00:00
import jace.Emulator ;
import jace.EmulatorUILogic ;
2014-09-07 21:10:04 +00:00
import jace.config.ConfigurableField ;
2016-02-21 08:22:47 +00:00
import jace.config.InvokableAction ;
2014-09-07 21:10:04 +00:00
import jace.core.Computer ;
2015-08-16 03:57:49 +00:00
import jace.core.RAM ;
2014-09-07 21:10:04 +00:00
import jace.core.RAMEvent ;
import jace.core.RAMListener ;
2015-08-13 04:44:44 +00:00
import java.util.Arrays ;
2014-09-07 21:10:04 +00:00
import java.util.HashSet ;
import java.util.Set ;
2015-02-03 06:55:25 +00:00
import javafx.scene.image.PixelWriter ;
import javafx.scene.image.WritableImage ;
2016-02-21 08:22:47 +00:00
import javafx.scene.paint.Color ;
2014-09-07 21:10:04 +00:00
2019-06-19 05:47:04 +00:00
import static jace.apple2e.VideoDHGR.BLACK ;
2014-09-07 21:10:04 +00:00
/ * *
* Provides a clean color monitor simulation , complete with text - friendly
* palette and mixed color / bw ( mode 7 ) rendering . This class extends the
* VideoDHGR class to provide all necessary video writers and other rendering
2015-02-03 06:55:25 +00:00
* mechanics , and then overrides the actual output routines ( showBW , showDhgr )
* with more suitable ( and much prettier ) alternatives . Rather than draw to the
* video buffer every cycle , rendered screen info is pushed into a buffer with
* mask bits ( to indicate B & W vs color ) And the actual conversion happens at the
* end of the scanline during the HBLANK period . This video rendering was
* inspired by Blargg but was ultimately rewritten from scratch once the color
* palette was implemented .
2014-09-07 21:10:04 +00:00
*
2015-02-03 06:55:25 +00:00
* @author Brendan Robert ( BLuRry ) brendan . robert @gmail.com
2014-09-07 21:10:04 +00:00
* /
public class VideoNTSC extends VideoDHGR {
@ConfigurableField ( name = " Text palette " , shortName = " textPalette " , defaultValue = " false " , description = " Use text-friendly color palette " )
2015-03-29 05:32:26 +00:00
public boolean useTextPalette = true ;
2015-08-12 07:08:22 +00:00
int activePalette [ ] [ ] = TEXT_PALETTE ;
2014-09-07 21:10:04 +00:00
@ConfigurableField ( name = " Video 7 " , shortName = " video7 " , defaultValue = " true " , description = " Enable Video 7 RGB rendering support " )
2015-03-29 05:32:26 +00:00
public boolean enableVideo7 = true ;
2014-09-07 21:10:04 +00:00
// Scanline represents 560 bits, divided up into 28-bit words
int [ ] scanline = new int [ 20 ] ;
2018-01-13 05:27:56 +00:00
static public int [ ] divBy28 = new int [ 560 ] ;
2014-09-07 21:10:04 +00:00
2019-06-19 05:47:04 +00:00
@ConfigurableField ( name = " Video Mode " , category = " video " , shortName = " mode " , defaultValue = " TextFriendly " , description = " Set Video Mode (Color|TextFriendly|Mode7|Mode7TextFriendly|Monochrome|Greenscreen|Amber) " )
public static VideoMode videoMode = VideoMode . TextFriendly ;
2014-09-07 21:10:04 +00:00
static {
for ( int i = 0 ; i < 560 ; i + + ) {
divBy28 [ i ] = i / 28 ;
}
}
2018-01-13 05:27:56 +00:00
protected boolean [ ] colorActive = new boolean [ 80 ] ;
2014-09-07 21:10:04 +00:00
int rowStart = 0 ;
2014-09-18 04:09:57 +00:00
public VideoNTSC ( Computer computer ) {
super ( computer ) ;
2015-08-16 03:57:49 +00:00
registerStateListeners ( ) ;
2014-09-18 04:09:57 +00:00
}
2016-02-21 08:22:47 +00:00
public static enum VideoMode {
Color ( " Color " ) ,
TextFriendly ( " Text-friendly color " ) ,
Mode7 ( " Mode7 Mixed RGB " ) ,
Mode7TextFriendly ( " Mode7 with Text-friendly palette " ) ,
Monochrome ( " Mono " ) ,
Greenscreen ( " Green " ) ,
Amber ( " Amber " ) ;
String name ;
2019-06-19 05:47:04 +00:00
2016-02-21 08:22:47 +00:00
VideoMode ( String n ) {
name = n ;
}
}
2019-06-19 05:47:04 +00:00
2016-02-21 08:22:47 +00:00
@InvokableAction ( name = " Toggle video mode " ,
category = " video " ,
2018-01-13 05:27:56 +00:00
alternatives = " Gfx mode;color;b&w;monochrome " ,
2016-02-21 08:22:47 +00:00
defaultKeyMapping = { " ctrl+shift+g " } )
public static void changeVideoMode ( ) {
VideoNTSC thiss = ( VideoNTSC ) Emulator . computer . video ;
2019-06-19 05:47:04 +00:00
int currentMode = Arrays . asList ( VideoMode . values ( ) ) . indexOf ( thiss . getVideoMode ( ) ) ;
2016-02-21 08:22:47 +00:00
currentMode + + ;
if ( currentMode > = VideoMode . values ( ) . length ) {
currentMode = 0 ;
}
2019-06-19 05:47:04 +00:00
thiss . setVideoMode ( VideoMode . values ( ) [ currentMode ] ) ;
}
public void setVideoMode ( VideoMode mode ) {
videoMode = mode ;
monochomeMode = false ;
2016-02-21 08:22:47 +00:00
WHITE = Color . WHITE ;
2019-06-19 05:47:04 +00:00
switch ( mode ) {
2016-02-21 08:22:47 +00:00
case Amber :
2019-06-19 05:47:04 +00:00
monochomeMode = true ;
2016-02-21 08:22:47 +00:00
WHITE = Color . web ( " ff8000 " ) ;
break ;
case Greenscreen :
2019-06-19 05:47:04 +00:00
monochomeMode = true ;
2016-02-21 08:22:47 +00:00
WHITE = Color . web ( " 0ccc68 " ) ;
break ;
case Monochrome :
2019-06-19 05:47:04 +00:00
monochomeMode = true ;
2016-02-21 08:22:47 +00:00
break ;
case Color :
2019-06-19 05:47:04 +00:00
useTextPalette = false ;
enableVideo7 = false ;
2016-02-21 08:22:47 +00:00
break ;
case Mode7 :
2019-06-19 05:47:04 +00:00
useTextPalette = false ;
enableVideo7 = true ;
2016-02-21 08:22:47 +00:00
break ;
case Mode7TextFriendly :
2019-06-19 05:47:04 +00:00
useTextPalette = true ;
enableVideo7 = true ;
2016-02-21 08:22:47 +00:00
break ;
case TextFriendly :
2019-06-19 05:47:04 +00:00
useTextPalette = true ;
enableVideo7 = false ;
2016-02-21 08:22:47 +00:00
break ;
}
2019-06-19 05:47:04 +00:00
activePalette = useTextPalette ? TEXT_PALETTE : SOLID_PALETTE ;
EmulatorUILogic . notify ( " Video mode: " + mode . name ) ;
2016-02-21 08:22:47 +00:00
forceRefresh ( ) ;
}
2019-06-19 05:47:04 +00:00
public VideoMode getVideoMode ( ) {
return videoMode ;
}
2014-09-07 21:10:04 +00:00
@Override
2015-03-31 01:36:45 +00:00
protected void showBW ( WritableImage screen , int x , int y , int dhgrWord ) {
int pos = divBy28 [ x ] ;
2015-08-13 04:44:44 +00:00
if ( rowStart < 0 ) {
rowStart = pos ;
}
2015-03-31 01:36:45 +00:00
colorActive [ pos * 4 ] = colorActive [ pos * 4 + 1 ] = colorActive [ pos * 4 + 2 ] = colorActive [ pos * 4 + 3 ] = false ;
scanline [ pos ] = dhgrWord ;
2014-09-07 21:10:04 +00:00
}
@Override
2015-03-31 01:36:45 +00:00
protected void showDhgr ( WritableImage screen , int x , int y , int dhgrWord ) {
int pos = divBy28 [ x ] ;
2015-08-13 04:44:44 +00:00
if ( rowStart < 0 ) {
rowStart = pos ;
}
2015-03-31 01:36:45 +00:00
colorActive [ pos * 4 ] = colorActive [ pos * 4 + 1 ] = colorActive [ pos * 4 + 2 ] = colorActive [ pos * 4 + 3 ] = true ;
scanline [ pos ] = dhgrWord ;
2014-09-07 21:10:04 +00:00
}
@Override
2015-02-03 06:55:25 +00:00
protected void displayLores ( WritableImage screen , int xOffset , int y , int rowAddress ) {
2016-01-03 22:44:09 +00:00
int data = ( ( RAM128k ) computer . getMemory ( ) ) . getMainMemory ( ) . readByte ( rowAddress + xOffset ) & 0x0FF ;
2015-03-31 01:36:45 +00:00
int pos = xOffset > > 1 ;
2015-08-13 04:44:44 +00:00
if ( rowStart < 0 ) {
rowStart = pos ;
}
2016-01-03 22:44:09 +00:00
colorActive [ xOffset * 2 ] = true ;
colorActive [ xOffset * 2 + 1 ] = true ;
2015-03-31 01:36:45 +00:00
if ( ( xOffset & 1 ) = = 0 ) {
2016-01-03 22:44:09 +00:00
int pat = scanline [ pos ] & 0x0fffc000 ;
2015-03-31 01:36:45 +00:00
if ( ( y & 7 ) < 4 ) {
data & = 15 ;
} else {
data > > = 4 ;
}
2016-01-03 22:44:09 +00:00
pat | = data | data < < 4 | data < < 8 | ( data & 3 ) < < 12 ;
2015-03-31 01:36:45 +00:00
scanline [ pos ] = pat ;
2014-09-07 21:10:04 +00:00
} else {
2016-01-03 22:44:09 +00:00
int pat = scanline [ pos ] & 0x03fff ;
2015-03-31 01:36:45 +00:00
if ( ( y & 7 ) < 4 ) {
data & = 15 ;
} else {
data > > = 4 ;
}
pat | = ( data & 12 ) < < 12 | data < < 16 | data < < 20 | data < < 24 ;
scanline [ pos ] = pat ;
2014-09-07 21:10:04 +00:00
}
}
2015-02-04 05:26:12 +00:00
@Override
protected void displayDoubleLores ( WritableImage screen , int xOffset , int y , int rowAddress ) {
2015-03-31 01:36:45 +00:00
int pos = xOffset > > 1 ;
2015-08-13 04:44:44 +00:00
if ( rowStart < 0 ) {
rowStart = pos ;
}
2015-03-31 01:36:45 +00:00
colorActive [ xOffset * 2 ] = colorActive [ xOffset * 2 + 1 ] = true ;
2015-02-04 05:26:12 +00:00
int c1 = ( ( RAM128k ) computer . getMemory ( ) ) . getAuxVideoMemory ( ) . readByte ( rowAddress + xOffset ) & 0x0FF ;
if ( ( y & 7 ) < 4 ) {
c1 & = 15 ;
} else {
c1 > > = 4 ;
}
int c2 = ( ( RAM128k ) computer . getMemory ( ) ) . getMainMemory ( ) . readByte ( rowAddress + xOffset ) & 0x0FF ;
if ( ( y & 7 ) < 4 ) {
c2 & = 15 ;
} else {
c2 > > = 4 ;
}
if ( ( xOffset & 0x01 ) = = 0 ) {
2015-02-09 07:13:14 +00:00
int pat = c1 | ( c1 & 7 ) < < 4 ;
pat | = c2 < < 7 | ( c2 & 7 ) < < 11 ;
2015-02-04 05:26:12 +00:00
scanline [ pos ] = pat ;
} else {
int pat = scanline [ pos ] ;
2015-02-09 07:13:14 +00:00
pat | = ( c1 & 12 ) < < 12 | c1 < < 16 | ( c1 & 1 ) < < 20 ;
pat | = ( c2 & 12 ) < < 19 | c2 < < 23 | ( c2 & 1 ) < < 27 ;
2015-02-04 05:26:12 +00:00
scanline [ pos ] = pat ;
}
}
2015-02-09 07:13:14 +00:00
2014-09-07 21:10:04 +00:00
@Override
2015-02-03 06:55:25 +00:00
public void hblankStart ( WritableImage screen , int y , boolean isDirty ) {
2014-09-07 21:10:04 +00:00
if ( isDirty ) {
renderScanline ( screen , y ) ;
}
}
// Offset is based on location in graphics buffer that corresponds with the row and
// a number (0-20) that represents how much of the scanline was rendered
// This is based off the xyOffset but is different because of P
static int pyOffset [ ] [ ] ;
static {
pyOffset = new int [ 192 ] [ 21 ] ;
for ( int y = 0 ; y < 192 ; y + + ) {
for ( int p = 0 ; p < 21 ; p + + ) {
pyOffset [ y ] [ p ] = ( y * 560 ) + ( p * 28 ) ;
}
}
}
2016-02-21 08:22:47 +00:00
boolean monochomeMode = false ;
2019-06-19 05:47:04 +00:00
2015-02-03 06:55:25 +00:00
private void renderScanline ( WritableImage screen , int y ) {
2015-03-31 01:36:45 +00:00
int p = 0 ;
2015-08-13 04:44:44 +00:00
if ( rowStart ! = 0 ) {
// getCurrentWriter().markDirty(y);
p = rowStart * 28 ;
if ( rowStart < 0 ) {
return ;
}
2015-03-31 01:36:45 +00:00
}
2015-08-13 04:44:44 +00:00
PixelWriter writer = screen . getPixelWriter ( ) ;
2015-03-31 01:36:45 +00:00
// Reset scanline position
int byteCounter = 0 ;
for ( int s = rowStart ; s < 20 ; s + + ) {
int add = 0 ;
int bits ;
if ( hiresMode ) {
bits = scanline [ s ] < < 2 ;
if ( s > 0 ) {
bits | = ( scanline [ s - 1 ] > > 26 ) & 3 ;
2014-09-07 21:10:04 +00:00
}
} else {
2015-03-31 01:36:45 +00:00
bits = scanline [ s ] < < 3 ;
if ( s > 0 ) {
bits | = ( scanline [ s - 1 ] > > 25 ) & 7 ;
2014-09-07 21:10:04 +00:00
}
}
2015-03-31 01:36:45 +00:00
if ( s < 19 ) {
add = ( scanline [ s + 1 ] & 7 ) ;
}
boolean isBW = false ;
2015-08-12 07:08:22 +00:00
boolean mixed = enableVideo7 & & dhgrMode & & graphicsMode = = rgbMode . MIX ;
2015-03-31 01:36:45 +00:00
for ( int i = 0 ; i < 28 ; i + + ) {
if ( i % 7 = = 0 ) {
2016-02-21 08:22:47 +00:00
isBW = monochomeMode | | ! colorActive [ byteCounter ] | | ( mixed & & ! hiresMode & & ! useColor [ byteCounter ] ) ;
2015-03-31 01:36:45 +00:00
byteCounter + + ;
}
if ( isBW ) {
writer . setColor ( p + + , y , ( ( bits & 0x8 ) = = 0 ) ? BLACK : WHITE ) ;
} else {
writer . setArgb ( p + + , y , activePalette [ i % 4 ] [ bits & 0x07f ] ) ;
}
bits > > = 1 ;
if ( i = = 20 ) {
bits | = add < < ( hiresMode ? 9 : 10 ) ;
}
}
// } else {
// for (int i = 0; i < 28; i++) {
// writer.setArgb(p++, y, activePalette[i % 4][bits & 0x07f]);
// bits >>= 1;
// if (i == 20) {
// bits |= add << (hiresMode ? 9 : 10);
// }
// }
// }
2014-09-07 21:10:04 +00:00
}
2015-08-13 04:44:44 +00:00
Arrays . fill ( scanline , 0 ) ;
rowStart = - 1 ;
2014-09-07 21:10:04 +00:00
}
// y Range [0,1]
public static final double MIN_Y = 0 ;
public static final double MAX_Y = 1 ;
// i Range [-0.5957, 0.5957]
public static final double MAX_I = 0 . 5957 ;
// q Range [-0.5226, 0.5226]
public static final double MAX_Q = 0 . 5226 ;
2015-08-12 07:08:22 +00:00
static final int SOLID_PALETTE [ ] [ ] = new int [ 4 ] [ 128 ] ;
static final int [ ] [ ] TEXT_PALETTE = new int [ 4 ] [ 128 ] ;
static final double [ ] [ ] YIQ_VALUES = {
2014-09-07 21:10:04 +00:00
{ 0 . 0 , 0 . 0 , 0 . 0 } , //0000 0
{ 0 . 25 , 0 . 5 , 0 . 5 } , //0001 1
{ 0 . 25 , - 0 . 5 , 0 . 5 } , //0010 2
{ 0 . 5 , 0 . 0 , 1 . 0 } , //0011 3 +Q
{ 0 . 25 , - 0 . 5 , - 0 . 5 } , //0100 4
{ 0 . 5 , 0 . 0 , 0 . 0 } , //0101 5
{ 0 . 5 , - 1 . 0 , 0 . 0 } , //0110 6 +I
{ 0 . 75 , - 0 . 5 , 0 . 5 } , //0111 7
{ 0 . 25 , 0 . 5 , - 0 . 5 } , //1000 8
{ 0 . 5 , 1 . 0 , 0 . 0 } , //1001 9 -I
{ 0 . 5 , 0 . 0 , 0 . 0 } , //1010 a
{ 0 . 75 , 0 . 5 , 0 . 5 } , //1011 b
{ 0 . 5 , 0 . 0 , - 1 . 0 } , //1100 c -Q
{ 0 . 75 , 0 . 5 , - 0 . 5 } , //1101 d
{ 0 . 75 , - 0 . 5 , - 0 . 5 } , //1110 e
{ 1 . 0 , 0 . 0 , 0 . 0 } , //1111 f
} ;
static {
int maxLevel = 10 ;
for ( int offset = 0 ; offset < 4 ; offset + + ) {
for ( int pattern = 0 ; pattern < 128 ; pattern + + ) {
int level = ( pattern & 1 )
+ ( ( pattern > > 1 ) & 1 ) * 1
+ ( ( pattern > > 2 ) & 1 ) * 2
+ ( ( pattern > > 3 ) & 1 ) * 4
+ ( ( pattern > > 4 ) & 1 ) * 2
+ ( ( pattern > > 5 ) & 1 ) * 1 ;
int col = ( pattern > > 2 ) & 0x0f ;
for ( int rot = 0 ; rot < offset ; rot + + ) {
col = ( ( col & 8 ) > > 3 ) | ( ( col < < 1 ) & 0x0f ) ;
}
2015-08-12 07:08:22 +00:00
double y1 = YIQ_VALUES [ col ] [ 0 ] ;
2015-08-16 03:57:49 +00:00
double y2 = ( level / ( double ) maxLevel ) ;
2015-08-12 07:08:22 +00:00
SOLID_PALETTE [ offset ] [ pattern ] = yiqToRgb ( y1 , YIQ_VALUES [ col ] [ 1 ] * MAX_I , YIQ_VALUES [ col ] [ 2 ] * MAX_Q ) ;
TEXT_PALETTE [ offset ] [ pattern ] = yiqToRgb ( y2 , YIQ_VALUES [ col ] [ 1 ] * MAX_I , YIQ_VALUES [ col ] [ 2 ] * MAX_Q ) ;
2014-09-07 21:10:04 +00:00
}
}
}
static public int yiqToRgb ( double y , double i , double q ) {
int r = ( int ) ( normalize ( ( y + 0 . 956 * i + 0 . 621 * q ) , 0 , 1 ) * 255 ) ;
int g = ( int ) ( normalize ( ( y - 0 . 272 * i - 0 . 647 * q ) , 0 , 1 ) * 255 ) ;
int b = ( int ) ( normalize ( ( y - 1 . 105 * i + 1 . 702 * q ) , 0 , 1 ) * 255 ) ;
2015-02-03 06:55:25 +00:00
return ( 255 < < 24 ) | ( r < < 16 ) | ( g < < 8 ) | b ;
2014-09-07 21:10:04 +00:00
}
public static double normalize ( double x , double minX , double maxX ) {
if ( x < minX ) {
return minX ;
}
if ( x > maxX ) {
return maxX ;
}
return x ;
}
@Override
public void reconfigure ( ) {
2019-06-19 05:47:04 +00:00
setVideoMode ( videoMode ) ;
2015-08-12 07:08:22 +00:00
activePalette = useTextPalette ? TEXT_PALETTE : SOLID_PALETTE ;
2014-09-07 21:10:04 +00:00
super . reconfigure ( ) ;
}
// The following section captures changes to the RGB mode
// The details of this are in Brodener's patent application #4631692
2019-06-19 05:47:04 +00:00
// http://www.freepatentsonline.com/4631692.pdf
2014-09-07 21:10:04 +00:00
// as well as the AppleColor adapter card manual
// http://apple2.info/download/Ext80ColumnAppleColorCardHR.pdf
2015-08-12 07:08:22 +00:00
rgbMode graphicsMode = rgbMode . MIX ;
2014-09-07 21:10:04 +00:00
public static enum rgbMode {
2015-08-12 07:08:22 +00:00
COLOR ( true ) , MIX ( true ) , BW ( false ) , COL_160 ( false ) ;
2014-09-07 21:10:04 +00:00
boolean colorMode = false ;
rgbMode ( boolean c ) {
this . colorMode = c ;
}
public boolean isColor ( ) {
return colorMode ;
}
}
public static enum ModeStateChanges {
SET_AN3 , CLEAR_AN3 , SET_80 , CLEAR_80 ;
}
boolean f1 = true ;
boolean f2 = true ;
2015-08-16 03:57:49 +00:00
boolean an3 = false ;
public void rgbStateChange ( ) {
2014-09-07 21:10:04 +00:00
// This is the more technically correct implementation except for two issues:
// 1) 160-column mode isn't implemented so it's not worth bothering to capture that state
// 2) A lot of programs are clueless about RGB modes so it's good to default to normal color mode
// graphicsMode = f1 ? (f2 ? rgbMode.color : rgbMode.mix) : (f2 ? rgbMode._160col : rgbMode.bw);
2015-08-12 07:08:22 +00:00
graphicsMode = f1 ? ( f2 ? rgbMode . COLOR : rgbMode . MIX ) : ( f2 ? rgbMode . COLOR : rgbMode . BW ) ;
2014-09-07 21:10:04 +00:00
}
// These catch changes to the RGB mode to toggle between color, BW and mixed
2014-09-18 04:09:57 +00:00
Set < RAMListener > rgbStateListeners = new HashSet < > ( ) ;
2014-09-07 21:10:04 +00:00
2015-08-16 03:57:49 +00:00
private void registerStateListeners ( ) {
if ( ! rgbStateListeners . isEmpty ( ) | | computer . getVideo ( ) ! = this ) {
return ;
}
RAM memory = computer . getMemory ( ) ;
rgbStateListeners . add ( memory . observe ( RAMEvent . TYPE . ANY , 0x0c05e , ( e ) - > {
an3 = false ;
rgbStateChange ( ) ;
} ) ) ;
rgbStateListeners . add ( memory . observe ( RAMEvent . TYPE . ANY , 0x0c05f , ( e ) - > {
if ( ! an3 ) {
f2 = f1 ;
f1 = SoftSwitches . _80COL . getState ( ) ;
2014-09-07 21:10:04 +00:00
}
2015-08-16 03:57:49 +00:00
an3 = true ;
rgbStateChange ( ) ;
} ) ) ;
rgbStateListeners . add ( memory . observe ( RAMEvent . TYPE . EXECUTE , 0x0fa62 , ( e ) - > {
// When reset hook is called, reset the graphics mode
2019-06-19 05:47:04 +00:00
// This is useful in case a program is running that
2015-08-16 03:57:49 +00:00
// is totally clueless how to set the RGB state correctly.
f1 = true ;
f2 = true ;
an3 = false ;
graphicsMode = rgbMode . COLOR ;
rgbStateChange ( ) ;
} ) ) ;
2014-09-07 21:10:04 +00:00
}
@Override
2015-08-16 03:57:49 +00:00
2014-09-07 21:10:04 +00:00
public void detach ( ) {
rgbStateListeners . stream ( ) . forEach ( ( l ) - > {
2014-09-18 04:09:57 +00:00
computer . getMemory ( ) . removeListener ( l ) ;
2014-09-07 21:10:04 +00:00
} ) ;
2015-08-16 03:57:49 +00:00
rgbStateListeners . clear ( ) ;
2015-02-21 16:52:26 +00:00
super . detach ( ) ;
2014-09-07 21:10:04 +00:00
}
@Override
public void attach ( ) {
super . attach ( ) ;
2015-08-16 03:57:49 +00:00
registerStateListeners ( ) ;
2014-09-07 21:10:04 +00:00
}
}