文章出處

本文轉自(http://www.cnblogs.com/aanbpsd/p/3920067.html

原作者故意寫錯了一點東西,這就讓那些一點腦筋也不想動的小伙伴得不到想要的結果。我在這里把那些地方糾正過來了。

使用Visual Studio SDK制作GLSL詞法著色插件

我們在Visual Studio上開發OpenGL ES項目時,避免不了寫Shader。這時在vs里直接編輯shader就會顯得很方便。但是vs默認是不支持GLSL的語法著色的,我們只好自己動手創造。最簡單的實現自定義語法著色的方法就是創建一個VSIX插件包,我們只需要安裝Visual Studio SDK,使用內置的模版就可以構建一個插件項目

1. 安裝Visual Studio SDK


http://www.microsoft.com/en-us/download/details.aspx?id=40758下載最新的Visual Studio 2013 SDK。

雙擊安裝,一路next即可。

安裝完畢后我們可以在新建項目->模版->C#中看到“擴展性”這一條目,這些就是開發插件用的模版了。

2. 創建插件項目


新建項目,在擴展性標簽中,選擇Editor Classifier模版,命名為ShaderEditor,點擊確定。

Visual Studio為我們生成了如下幾個文件。

ShaderEditorFormat.cs文件的默認代碼如下: 

復制代碼
 1     [Export(typeof(EditorFormatDefinition))]
 2     [ClassificationType(ClassificationTypeNames = "ShaderEditor")]
 3     [Name("ShaderEditor")]
 4     [UserVisible(true)] //this should be visible to the end user
 5     [Order(Before = Priority.Default)] //set the priority to be after the default classifiers
 6     internal sealed class ShaderEditorFormat : ClassificationFormatDefinition {
 7         /// <summary>
 8         /// Defines the visual format for the "ShaderEditor" classification type
 9         /// </summary>
10         public ShaderEditorFormat() {
11             this.DisplayName = "ShaderEditor"; //human readable version of the name
12             this.BackgroundColor = Colors.BlueViolet;
13             this.TextDecorations = System.Windows.TextDecorations.Underline;
14         }
15     }
復制代碼

 

這段代碼定義了一個名為"ShaderEditor"的著色類型,編譯工程并運行,我們可以在Visual Studio實驗實例的工具->選項->字體和顏色中找到一個名為"ShaderEditor"的條目。同時我們會發現所有文本文件的顏色都變成了Colors.BlueViolet并帶上了下劃線。修改this.DisplayName = "ShaderEditor"的內容,可以改變在字體和顏色中顯示的名字。下面的格式設置可以任意修改成喜歡的樣式,但要注意在這里的格式只是插件首次安裝時的默認設置,這些條目和其它著色選項一樣,都可以被用戶任意更改。

3. 創建GLSL的著色類型



我們已經了解了如何將著色類型添加到Visual Studio,現在修改ShaderEditorFormat.cs,添加我們的著色類型。

  1     #region Format definition
  2     [Export(typeof(EditorFormatDefinition))]
  3     [ClassificationType(ClassificationTypeNames = "GLSLText")]
  4     [Name("GLSLText")]
  5     [UserVisible(true)]
  6     [Order(Before = Priority.Default)]
  7     internal sealed class GLSLTextFormatDefinition : ClassificationFormatDefinition
  8     {
  9         public GLSLTextFormatDefinition()
 10         {
 11             this.DisplayName = "GLSL文本";
 12             this.ForegroundColor = Colors.Brown;
 13         }
 14     }
 15 
 16     [Export(typeof(EditorFormatDefinition))]
 17     [ClassificationType(ClassificationTypeNames = "GLSLIdentifier")]
 18     [Name("GLSLIdentifier")]
 19     [UserVisible(true)]
 20     [Order(Before = Priority.Default)]
 21     internal sealed class GLSLIdentifierFormatDefinition : ClassificationFormatDefinition
 22     {
 23         public GLSLIdentifierFormatDefinition()
 24         {
 25             this.DisplayName = "GLSL標識符";
 26             this.ForegroundColor = Colors.Brown;
 27         }
 28     }
 29 
 30     [Export(typeof(EditorFormatDefinition))]
 31     [ClassificationType(ClassificationTypeNames = "GLSLComment")]
 32     [Name("GLSLComment")]
 33     [UserVisible(true)]
 34     [Order(Before = Priority.Default)]
 35     internal sealed class GLSLCommentFormatDefinition : ClassificationFormatDefinition
 36     {
 37         public GLSLCommentFormatDefinition()
 38         {
 39             this.DisplayName = "GLSL注釋";
 40             this.ForegroundColor = Colors.DarkGray;
 41         }
 42     }
 43 
 44     [Export(typeof(EditorFormatDefinition))]
 45     [ClassificationType(ClassificationTypeNames = "GLSLKeyword")]
 46     [Name("GLSLKeyword")]
 47     [UserVisible(true)]
 48     [Order(Before = Priority.Default)]
 49     internal sealed class GLSLKeywordFormatDefinition : ClassificationFormatDefinition
 50     {
 51         public GLSLKeywordFormatDefinition()
 52         {
 53             this.DisplayName = "GLSL關鍵字";
 54             this.ForegroundColor = Colors.Blue;
 55         }
 56     }
 57 
 58     [Export(typeof(EditorFormatDefinition))]
 59     [ClassificationType(ClassificationTypeNames = "GLSLClass")]
 60     [Name("GLSLClass")]
 61     [UserVisible(true)]
 62     [Order(Before = Priority.Default)]
 63     internal sealed class GLSLClassFormatDefinition : ClassificationFormatDefinition
 64     {
 65         public GLSLClassFormatDefinition()
 66         {
 67             this.DisplayName = "GLSL類型";
 68             this.ForegroundColor = Colors.Green;
 69         }
 70     }
 71 
 72     [Export(typeof(EditorFormatDefinition))]
 73     [ClassificationType(ClassificationTypeNames = "GLSLQualifier")]
 74     [Name("GLSLQualifier")]
 75     [UserVisible(true)]
 76     [Order(Before = Priority.Default)]
 77     internal sealed class GLSLQualifierFormatDefinition : ClassificationFormatDefinition
 78     {
 79         public GLSLQualifierFormatDefinition()
 80         {
 81             this.DisplayName = "GLSL限定符";
 82             this.ForegroundColor = Colors.Pink;
 83         }
 84     }
 85 
 86     [Export(typeof(EditorFormatDefinition))]
 87     [ClassificationType(ClassificationTypeNames = "GLSLVariable")]
 88     [Name("GLSLVariable")]
 89     [UserVisible(true)]
 90     [Order(Before = Priority.Default)]
 91     internal sealed class GLSLVariableFormatDefinition : ClassificationFormatDefinition
 92     {
 93         public GLSLVariableFormatDefinition()
 94         {
 95             this.DisplayName = "GLSL系統變量";
 96             this.ForegroundColor = Colors.DarkOrange;
 97         }
 98     }
 99 
100     [Export(typeof(EditorFormatDefinition))]
101     [ClassificationType(ClassificationTypeNames = "GLSLFunction")]
102     [Name("GLSLFunction")]
103     [UserVisible(true)]
104     [Order(Before = Priority.Default)]
105     internal sealed class GLSLFunctionFormatDefinition : ClassificationFormatDefinition
106     {
107         public GLSLFunctionFormatDefinition()
108         {
109             this.DisplayName = "GLSL系統函數";
110             this.ForegroundColor = Colors.DarkTurquoise;
111         }
112     }
113     #endregion //Format definition

 

4. 導出著色類型


Editor Classifier使用了MEF框架,關于MEF的具體細節,請參考MSDN的相關文檔。

我們需要注意的是,在MEF中,光定義了著色類型還不夠,我們需要導出一個ClassificationTypeDefinition,才能在系統中生效。

打開ShaderEditorType.cs,我們看到系統生成的代碼如下:

1     internal static class ShaderEditorClassificationDefinition {
2         [Export(typeof(ClassificationTypeDefinition))]
3         [Name("ShaderEditor")]
4         internal static ClassificationTypeDefinition ShaderEditorType = null;
5     }

這里的Name與之前默認生成的ShaderEditor相同,同理,我們將這里的代碼修改成方才定義的類型

 1     internal static class ShaderEditorClassificationDefinition
 2     {
 3         [Export(typeof(ClassificationTypeDefinition))]
 4         [Name("GLSLText")]
 5         internal static ClassificationTypeDefinition GLSLTextType = null;
 6 
 7         [Export(typeof(ClassificationTypeDefinition))]
 8         [Name("GLSLIdentifier")]
 9         internal static ClassificationTypeDefinition GLSLIdentifierType = null;
10 
11         [Export(typeof(ClassificationTypeDefinition))]
12         [Name("GLSLComment")]
13         internal static ClassificationTypeDefinition GLSLCommentType = null;
14 
15         [Export(typeof(ClassificationTypeDefinition))]
16         [Name("GLSLKeyword")]
17         internal static ClassificationTypeDefinition GLSLKeywordType = null;
18 
19         [Export(typeof(ClassificationTypeDefinition))]
20         [Name("GLSLClass")]
21         internal static ClassificationTypeDefinition GLSLClassType = null;
22 
23         [Export(typeof(ClassificationTypeDefinition))]
24         [Name("GLSLQualifier")]
25         internal static ClassificationTypeDefinition GLSLQualifierType = null;
26 
27         [Export(typeof(ClassificationTypeDefinition))]
28         [Name("GLSLVariable")]
29         internal static ClassificationTypeDefinition GLSLVariableType = null;
30 
31         [Export(typeof(ClassificationTypeDefinition))]
32         [Name("GLSLFunction")]
33         internal static ClassificationTypeDefinition GLSLFunctionType = null;
34     }

 

5. 關聯文件類型


打開ShaderEditor.cs

復制代碼
 1     [Export(typeof(IClassifierProvider))]
 2     [ContentType("text")]
 3     internal class ShaderEditorProvider : IClassifierProvider {
 4         [Import]
 5         internal IClassificationTypeRegistryService ClassificationRegistry = null; // Set via MEF
 6 
 7         public IClassifier GetClassifier(ITextBuffer buffer) {
 8             return buffer.Properties.GetOrCreateSingletonProperty<ShaderEditor>(delegate { return new ShaderEditor(ClassificationRegistry); });
 9         }
10     }
復制代碼

代碼ContentType("text")創建了一個Provider并將它們對所有text類型的文件生效。

GLSL主要的文件擴展名為.vsh和.fsh,為了只對這兩個擴展名生效,我們需要自定義一個ContentType,并創建兩個擴展名關聯。將上述代碼修改為:

 1     [Export(typeof(ITaggerProvider))]
 2     [ContentType("glsl")]
 3     [TagType(typeof(ClassificationTag))]
 4     internal sealed class GLSLClassifierProvider : ITaggerProvider {
 5 
 6         [Export]
 7         [Name("glsl")]
 8         [BaseDefinition("code")]
 9         internal static ContentTypeDefinition GLSLContentType = null;
10 
11         [Export]
12         [FileExtension(".vsh")]
13         [ContentType("glsl")]
14         internal static FileExtensionToContentTypeDefinition GLSLVshType = null;
15 
16         [Export]
17         [FileExtension(".fsh")]
18         [ContentType("glsl")]
19         internal static FileExtensionToContentTypeDefinition GLSLFshType = null;
20 
21         [Import]
22         internal IClassificationTypeRegistryService classificationTypeRegistry = null;
23 
24         [Import]
25         internal IBufferTagAggregatorFactoryService aggregatorFactory = null;
26 
27         public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag {
28             return new GLSLClassifier(buffer, classificationTypeRegistry) as ITagger<T>;
29         }
30     }

(這段代碼有問題,請用我改過的)

 1     [Export(typeof(ITaggerProvider))]
 2     [ContentType("glsl")]
 3     [TagType(typeof(ClassificationTag))]
 4     internal sealed class GLSLClassifierProvider : ITaggerProvider
 5     {
 6 
 7         [Export]
 8         [Name("glsl")]
 9         [BaseDefinition("code")]
10         internal static ContentTypeDefinition GLSLContentType = null;
11 
12         [Export]
13         [FileExtension(".vert")]
14         [ContentType("glsl")]
15         internal static FileExtensionToContentTypeDefinition GLSLVshType = null;
16 
17         [Export]
18         [FileExtension(".frag")]
19         [ContentType("glsl")]
20         internal static FileExtensionToContentTypeDefinition GLSLFshType = null;
21 
22         [Export]
23         [FileExtension(".geom")]
24         [ContentType("glsl")]
25         internal static FileExtensionToContentTypeDefinition GLSLGshType = null;
26 
27         [Import]
28         internal IClassificationTypeRegistryService classificationTypeRegistry = null;
29 
30         [Import]
31         internal IBufferTagAggregatorFactoryService aggregatorFactory = null;
32 
33         public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
34         {
35             return new GLSLClassifier(buffer, classificationTypeRegistry) as ITagger<T>;
36         }
37     }

這樣我們就創建了只針對vert、frag和geom文件生效的Editor。也就是頂點Shader、片段Shader和幾何Shader。

 

6. 使用gplex進行詞法分析


我們需要使用詞法分析掃描器來實現具體的著色功能,gplex可以為我們生成C#語言的掃描器,下載地址:

http://gplex.codeplex.com/

解壓后在binaries文件夾下找到gplex.exe,把它拷貝到項目的根目錄下。

在項目根目錄下新建一個GLSL文件夾,新建GLSLLexer.lex文件。并把它們添加到proj中。

在proj上右鍵->屬性,在生成事件選項卡中,在預先生成事件命令行中輸入

cd $(ProjectDir)GLSL\
$(ProjectDir)\gplex GLSLLexer

打開GLSLLexer.lex,寫入以下代碼:

復制代碼
 1 %option unicode, codepage:raw
 2 
 3 %{
 4         // User code is all now in ScanHelper.cs
 5 %}
 6 
 7 %namespace Shane
 8 %option verbose, summary, noparser, nofiles, unicode
 9 
10 %{
11     public int nextToken() { return yylex(); }
12     public int getPos() { return yypos; }
13     public int getLength() { return yyleng; }
14 %}
15 
16 //=============================================================
17 //=============================================================
18 
19 number ([0-9])+
20 chars [A-Za-z]
21 cstring [A-Za-z_]
22 blank " "
23 delim [ \t\n]
24 word {chars}+
25 singleLineComment "//"[^\n]*
26 multiLineComment "/*"[^*]*\*(\*|([^*/]([^*])*\*))*\/
27 
28 comment {multiLineComment}|{singleLineComment}
29 class bool|int|float|bvec|ivec|vec|vec2|vec3|vec4|mat2|mat3|mat4|sampler1D|sampler2D|sampler3D|samplerCube|sampler1DShadow|sampler2DShadow
30 keyword return|if|else|while|do|for|foreach|break|continue|switch|case|default|goto|class|struct|enum|extern|interface|namespace|public|static|this|volatile|using|in|out|true|false
31 qualifier const|attribute|uniform|varying
32 systemVariable gl_BackColor|gl_BackLightModelProduct|gl_BackLightProduct|gl_BackMaterial|gl_BackSecondaryColor|gl_ClipPlane|gl_ClipVertex|gl_Color|gl_DepthRange|gl_DepthRangeParameters|gl_EyePlaneQ|gl_EyePlaneR|gl_EyePlaneS|gl_EyePlaneT|gl_Fog|gl_FogCoord|gl_FogFragCoord|gl_FogParameters|gl_FragColor|gl_FragCoord|gl_FragData|gl_FragDepth|gl_FrontColor|gl_FrontFacing|gl_FrontLightModelProduct|gl_FrontLightProduct|gl_FrontMaterial|gl_FrontSecondaryColor|gl_LightModel|gl_LightModelParameters|gl_LightModelProducts|gl_LightProducts|gl_LightSource|gl_LightSourceParameters|gl_MaterialParameters|gl_MaxClipPlanes|gl_MaxCombinedTextureImageUnits|gl_MaxDrawBuffers|gl_MaxFragmentUniformComponents|gl_MaxLights|gl_MaxTextureCoords|gl_MaxTextureImageUnits|gl_MaxTextureUnits|gl_MaxVaryingFloats|gl_MaxVertexAttribs|gl_MaxVertexTextureImageUnits|gl_MaxVertexUniformComponents|gl_ModelViewMatrix|gl_ModelViewMatrixInverse|gl_ModelViewMatrixInverseTranspose|gl_ModelViewMatrixTranspose|gl_ModelViewProjectionMatrix|gl_ModelViewProjectionMatrixInverse|gl_ModelViewProjectionMatrixInverseTranspose|gl_ModelViewProjectionMatrixTranspose|gl_MultiTexCoord0|gl_MultiTexCoord1|gl_MultiTexCoord10|gl_MultiTexCoord11|gl_MultiTexCoord2|gl_MultiTexCoord3|gl_MultiTexCoord4|gl_MultiTexCoord5|gl_MultiTexCoord6|gl_MultiTexCoord7|gl_MultiTexCoord8|gl_MultiTexCoord9|gl_Normal|gl_NormalMatrix|gl_NormalScale|gl_ObjectPlaneQ|gl_ObjectPlaneR|gl_ObjectPlaneS|gl_ObjectPlaneT|gl_Point|gl_PointParameters|gl_PointSize|gl_Position|gl_ProjectionMatrix|gl_ProjectionMatrixInverse|gl_ProjectionMatrixInverseTranspose|gl_ProjectionMatrixTranspose|gl_SecondaryColor|gl_TexCoord|gl_TextureEnvColor|gl_TextureMatrix|gl_TextureMatrixInverse|gl_TextureMatrixInverseTranspose|gl_TextureMatrixTranspose|gl_Vertex
33 systemFunction radians|degress|sin|cos|tan|asin|acos|atan|pow|exp|log|exp2|log2|sqrt|inversesqrt|abs|sign|floor|ceil|fract|mod|min|max|clamp|mix|step|smoothstep|length|distance|dot|cross|normalize|faceforward|reflect|matrixCompMult|lessThan|lessThanEqual|greaterThan|greaterThanEqual|equal|notEqual|any|all|not|texture2D|texture2DProj|texture2DLod|texture2DProjLod|textureCube|textureCubeLod
34 identifier {cstring}+{number}*[{cstring}@]*{number}*
35 
36 %%
37 
38 {keyword}            return (int)GLSLTokenType.Keyword;
39 {class}                return (int)GLSLTokenType.Class;
40 {qualifier}            return (int)GLSLTokenType.Qualifier;
41 {systemVariable}    return (int)GLSLTokenType.SystemVariable;
42 {systemFunction}    return (int)GLSLTokenType.SystemFunction;
43 {identifier}        return (int)GLSLTokenType.Identifier;
44 {comment}            return (int)GLSLTokenType.Comment;
45 
46 %%
復制代碼

保存并關閉,這時生成一下項目,我們會看到在GLSL目錄下生成了GLSLLexer.cs文件,同樣把這個文件添加到proj中。

如果你很懶,可以直接用這個(點此下載),這是我已經生成了的GLSLLexer.cs。(源碼太大,多次上傳失敗)

 

7. 處理掃描結果 


接下來我們要在ShaderEditor.cs中處理我們的掃描結果,并最終對匹配的代碼行進行著色。

首先刪除默認創建的ShaderEditor類。

添加一個GLSLToken枚舉,這個枚舉就是GLSLLexer.cs返回的枚舉類型,它用來通知我們當前的語句塊是哪個類型。

代碼如下:

 1     public enum GLSLTokenType
 2     {
 3         Text = 1,
 4         Keyword,
 5         Comment,
 6         Identifier,
 7         Class,
 8         Qualifier,
 9         SystemVariable,
10         SystemFunction
11     }

 

創建我們自己的ShaderEditor類,代碼如下:

  1     #region Provider definition
  2     [Export(typeof(ITaggerProvider))]
  3     [ContentType("glsl")]
  4     [TagType(typeof(ClassificationTag))]
  5     internal sealed class GLSLClassifierProvider : ITaggerProvider
  6     {
  7 
  8         [Export]
  9         [Name("glsl")]
 10         [BaseDefinition("code")]
 11         internal static ContentTypeDefinition GLSLContentType = null;
 12 
 13         [Export]
 14         [FileExtension(".vert")]
 15         [ContentType("glsl")]
 16         internal static FileExtensionToContentTypeDefinition GLSLVshType = null;
 17 
 18         [Export]
 19         [FileExtension(".frag")]
 20         [ContentType("glsl")]
 21         internal static FileExtensionToContentTypeDefinition GLSLFshType = null;
 22 
 23         [Export]
 24         [FileExtension(".geom")]
 25         [ContentType("glsl")]
 26         internal static FileExtensionToContentTypeDefinition GLSLGshType = null;
 27 
 28         [Import]
 29         internal IClassificationTypeRegistryService classificationTypeRegistry = null;
 30 
 31         [Import]
 32         internal IBufferTagAggregatorFactoryService aggregatorFactory = null;
 33 
 34         public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
 35         {
 36             return new GLSLClassifier(buffer, classificationTypeRegistry) as ITagger<T>;
 37         }
 38     }
 39     #endregion //provider def
 40 
 41     #region Classifier
 42     internal sealed class GLSLClassifier : ITagger<ClassificationTag>
 43     {
 44         internal GLSLClassifier(ITextBuffer buffer, IClassificationTypeRegistryService typeService)
 45         {
 46             textBuffer = buffer;
 47             typeDic = new Dictionary<GLSLTokenType, IClassificationType>();
 48             typeDic[GLSLTokenType.Text] = typeService.GetClassificationType("GLSLText");
 49             typeDic[GLSLTokenType.Identifier] = typeService.GetClassificationType("GLSLIdentifier");
 50             typeDic[GLSLTokenType.Keyword] = typeService.GetClassificationType("GLSLKeyword");
 51             typeDic[GLSLTokenType.Class] = typeService.GetClassificationType("GLSLClass");
 52             typeDic[GLSLTokenType.Comment] = typeService.GetClassificationType("GLSLComment");
 53             typeDic[GLSLTokenType.Qualifier] = typeService.GetClassificationType("GLSLQualifier");
 54             typeDic[GLSLTokenType.SystemVariable] = typeService.GetClassificationType("GLSLVariable");
 55             typeDic[GLSLTokenType.SystemFunction] = typeService.GetClassificationType("GLSLFunction");
 56         }
 57 
 58         public event EventHandler<SnapshotSpanEventArgs> TagsChanged
 59         {
 60             add { }
 61             remove { }
 62         }
 63 
 64         public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
 65         {
 66             IClassificationType classification = typeDic[GLSLTokenType.Text];
 67             string text = spans[0].Snapshot.GetText();
 68             yield return new TagSpan<ClassificationTag>(
 69                 new SnapshotSpan(spans[0].Snapshot, new Span(0, text.Length)),
 70                 new ClassificationTag(classification));
 71             scanner.SetSource(text, 0);
 72             int tok;
 73             do
 74             {
 75                 tok = scanner.nextToken();
 76                 int pos = scanner.getPos();
 77                 int len = scanner.getLength();
 78                 int total = text.Length;
 79                 if (pos < 0 || len < 0 || pos > total)
 80                 {
 81                     continue;
 82                 }
 83                 if (pos + len > total)
 84                 {
 85                     len = total - pos;
 86                 }
 87                 if (typeDic.TryGetValue((GLSLTokenType)tok, out classification))
 88                 {
 89                     yield return new TagSpan<ClassificationTag>(
 90                         new SnapshotSpan(spans[0].Snapshot, new Span(pos, len)),
 91                         new ClassificationTag(classification));
 92                 }
 93             } while (tok > (int)Tokens.EOF);
 94         }
 95 
 96         ITextBuffer textBuffer;
 97         IDictionary<GLSLTokenType, IClassificationType> typeDic;
 98         Scanner scanner = new Scanner();
 99     }
100     #endregion //Classifier
GLSLClassifierProvider

TagsChanged事件保證在代碼發生改變時實時刷新編輯器。

構造方法中,通過typeService.GetClassificationType("GLSLIdentifier")取得我們定義的實例,并把它們和枚舉關聯起來,

GetClassificationType的參數傳入著色類型的Name。

GetTags方法是由系統調用的迭代方法,yield return new TagSpan<ClassificationTag>()返回我們的著色對象,即可實現著色效果。

編譯并運行,可以看到vert、fraghe geom已經有了語法著色了。

 

如果你實在做不出來,不如去我的Github下載源碼,直接編譯即可。

2016-05-14

根據GLSL4.3更新了GLSL的關鍵字、內置類型、內置變量、內置函數,添加了對“數值”的高亮顯示。

重命名為GLGLHighlight。

為了方便換色,我把常用顏色保存到這里

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()